@insforge/cli 0.1.58 → 0.1.61
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 +296 -195
- 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 join13, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import * as clack11 from "@clack/prompts";
|
|
@@ -41,8 +41,8 @@ var LineReader = class {
|
|
|
41
41
|
this.output.write(prompt);
|
|
42
42
|
if (this.queue.length > 0) return this.queue.shift();
|
|
43
43
|
if (this.closed) return null;
|
|
44
|
-
return new Promise((
|
|
45
|
-
this.waiter =
|
|
44
|
+
return new Promise((resolve5) => {
|
|
45
|
+
this.waiter = resolve5;
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
close() {
|
|
@@ -411,8 +411,8 @@ function startCallbackServer() {
|
|
|
411
411
|
return new Promise((resolveServer) => {
|
|
412
412
|
let resolveResult;
|
|
413
413
|
let rejectResult;
|
|
414
|
-
const resultPromise = new Promise((
|
|
415
|
-
resolveResult =
|
|
414
|
+
const resultPromise = new Promise((resolve5, reject) => {
|
|
415
|
+
resolveResult = resolve5;
|
|
416
416
|
rejectResult = reject;
|
|
417
417
|
});
|
|
418
418
|
const server = createServer((req, res) => {
|
|
@@ -748,7 +748,7 @@ async function reportAgentConnected(payload, apiUrl) {
|
|
|
748
748
|
await fetch(`${baseUrl}/tracking/v1/agent-connected`, {
|
|
749
749
|
method: "POST",
|
|
750
750
|
headers,
|
|
751
|
-
body: JSON.stringify(payload)
|
|
751
|
+
body: JSON.stringify({ ...payload, client: "cli" })
|
|
752
752
|
});
|
|
753
753
|
}
|
|
754
754
|
async function streamDiagnosticAnalysis(payload, onEvent, apiUrl) {
|
|
@@ -1466,11 +1466,11 @@ async function collectDeploymentFiles(sourceDir) {
|
|
|
1466
1466
|
return files;
|
|
1467
1467
|
}
|
|
1468
1468
|
async function createZipBuffer(sourceDir) {
|
|
1469
|
-
return new Promise((
|
|
1469
|
+
return new Promise((resolve5, reject) => {
|
|
1470
1470
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1471
1471
|
const chunks = [];
|
|
1472
1472
|
archive.on("data", (chunk) => chunks.push(chunk));
|
|
1473
|
-
archive.on("end", () =>
|
|
1473
|
+
archive.on("end", () => resolve5(Buffer.concat(chunks)));
|
|
1474
1474
|
archive.on("error", (err) => reject(err));
|
|
1475
1475
|
archive.directory(sourceDir, false, (entry) => {
|
|
1476
1476
|
if (shouldExclude(entry.name)) return false;
|
|
@@ -1552,7 +1552,7 @@ async function pollDeployment(deploymentId, spinner7, syncBeforeRead) {
|
|
|
1552
1552
|
const startTime = Date.now();
|
|
1553
1553
|
let deployment = null;
|
|
1554
1554
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1555
|
-
await new Promise((
|
|
1555
|
+
await new Promise((resolve5) => setTimeout(resolve5, POLL_INTERVAL_MS));
|
|
1556
1556
|
try {
|
|
1557
1557
|
if (syncBeforeRead) {
|
|
1558
1558
|
await ossFetch(`/api/deployments/${deploymentId}/sync`, { method: "POST" });
|
|
@@ -2869,13 +2869,17 @@ import { join as join8 } from "path";
|
|
|
2869
2869
|
// src/lib/migrations.ts
|
|
2870
2870
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2871
2871
|
import { join as join7 } from "path";
|
|
2872
|
-
var MIGRATION_VERSION_REGEX = /^\d{
|
|
2873
|
-
var MIGRATION_FILENAME_REGEX = /^(\d{
|
|
2872
|
+
var MIGRATION_VERSION_REGEX = /^\d{1,64}$/u;
|
|
2873
|
+
var MIGRATION_FILENAME_REGEX = /^(\d{1,64})_([a-z0-9-]+)\.sql$/u;
|
|
2874
2874
|
function assertValidMigrationVersion(version) {
|
|
2875
2875
|
if (!MIGRATION_VERSION_REGEX.test(version)) {
|
|
2876
|
-
throw new CLIError(`Invalid migration version: ${version}. Expected
|
|
2876
|
+
throw new CLIError(`Invalid migration version: ${version}. Expected a numeric string of at most 64 digits (e.g. 0001 or 20260418091500).`);
|
|
2877
2877
|
}
|
|
2878
2878
|
}
|
|
2879
|
+
function canonicalMigrationVersion(version) {
|
|
2880
|
+
assertValidMigrationVersion(version);
|
|
2881
|
+
return BigInt(version).toString();
|
|
2882
|
+
}
|
|
2879
2883
|
function parseMigrationFilename(filename) {
|
|
2880
2884
|
const match = MIGRATION_FILENAME_REGEX.exec(filename);
|
|
2881
2885
|
if (!match) {
|
|
@@ -2883,11 +2887,16 @@ function parseMigrationFilename(filename) {
|
|
|
2883
2887
|
}
|
|
2884
2888
|
return {
|
|
2885
2889
|
filename,
|
|
2886
|
-
version: match[1],
|
|
2890
|
+
version: canonicalMigrationVersion(match[1]),
|
|
2887
2891
|
name: match[2]
|
|
2888
2892
|
};
|
|
2889
2893
|
}
|
|
2890
2894
|
function compareMigrationVersions(left, right) {
|
|
2895
|
+
if (MIGRATION_VERSION_REGEX.test(left) && MIGRATION_VERSION_REGEX.test(right)) {
|
|
2896
|
+
const a = BigInt(left);
|
|
2897
|
+
const b = BigInt(right);
|
|
2898
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2899
|
+
}
|
|
2891
2900
|
return left.localeCompare(right);
|
|
2892
2901
|
}
|
|
2893
2902
|
function getRemoteMigrationVersionStatus(version, appliedVersions, latestRemoteVersion) {
|
|
@@ -2909,6 +2918,10 @@ function formatMigrationVersion(date) {
|
|
|
2909
2918
|
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
2910
2919
|
}
|
|
2911
2920
|
function incrementMigrationVersion(version) {
|
|
2921
|
+
assertValidMigrationVersion(version);
|
|
2922
|
+
if (!/^\d{14}$/u.test(version)) {
|
|
2923
|
+
return String(BigInt(version) + 1n);
|
|
2924
|
+
}
|
|
2912
2925
|
const year = Number(version.slice(0, 4));
|
|
2913
2926
|
const month = Number(version.slice(4, 6)) - 1;
|
|
2914
2927
|
const day = Number(version.slice(6, 8));
|
|
@@ -2991,8 +3004,9 @@ function findOlderThanHeadLocalMigrations(migrations, appliedVersions, latestRem
|
|
|
2991
3004
|
);
|
|
2992
3005
|
}
|
|
2993
3006
|
function findLocalMigrationByVersion(version, filenames) {
|
|
3007
|
+
const canonicalVersion = canonicalMigrationVersion(version);
|
|
2994
3008
|
const matches = filenames.map((filename) => parseMigrationFilename(filename)).filter(
|
|
2995
|
-
(migration) => migration !== null && migration.version ===
|
|
3009
|
+
(migration) => migration !== null && migration.version === canonicalVersion
|
|
2996
3010
|
);
|
|
2997
3011
|
if (matches.length === 0) {
|
|
2998
3012
|
throw new CLIError(`Local migration for version ${version} not found.`);
|
|
@@ -3005,7 +3019,7 @@ function findLocalMigrationByVersion(version, filenames) {
|
|
|
3005
3019
|
return matches[0];
|
|
3006
3020
|
}
|
|
3007
3021
|
function resolveMigrationTarget(target, filenames) {
|
|
3008
|
-
if (/^\d{
|
|
3022
|
+
if (/^\d{1,64}$/u.test(target)) {
|
|
3009
3023
|
return findLocalMigrationByVersion(target, filenames);
|
|
3010
3024
|
}
|
|
3011
3025
|
const parsedTarget = parseMigrationFilename(target);
|
|
@@ -3044,7 +3058,7 @@ async function fetchRemoteMigrations() {
|
|
|
3044
3058
|
const raw = await res.json();
|
|
3045
3059
|
const migrations = Array.isArray(raw.migrations) ? raw.migrations : [];
|
|
3046
3060
|
for (const migration of migrations) {
|
|
3047
|
-
|
|
3061
|
+
migration.version = canonicalMigrationVersion(migration.version);
|
|
3048
3062
|
}
|
|
3049
3063
|
return migrations;
|
|
3050
3064
|
}
|
|
@@ -3104,6 +3118,9 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
3104
3118
|
await requireAuth();
|
|
3105
3119
|
const migrations = await fetchRemoteMigrations();
|
|
3106
3120
|
const migrationsDir = ensureMigrationsDir();
|
|
3121
|
+
const existingLocalVersions = new Set(
|
|
3122
|
+
listLocalMigrationFilenames().map((filename) => parseMigrationFilename(filename)).filter((migration) => migration !== null).map((migration) => migration.version)
|
|
3123
|
+
);
|
|
3107
3124
|
const createdFiles = [];
|
|
3108
3125
|
const skippedFiles = [];
|
|
3109
3126
|
for (const migration of [...migrations].sort(
|
|
@@ -3115,12 +3132,13 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
3115
3132
|
migration.name
|
|
3116
3133
|
);
|
|
3117
3134
|
const filePath = join8(migrationsDir, filename);
|
|
3118
|
-
if (existsSync4(filePath)) {
|
|
3135
|
+
if (existingLocalVersions.has(migration.version) || existsSync4(filePath)) {
|
|
3119
3136
|
skippedFiles.push(filename);
|
|
3120
3137
|
continue;
|
|
3121
3138
|
}
|
|
3122
3139
|
writeFileSync3(filePath, formatMigrationSql(migration.statements));
|
|
3123
3140
|
createdFiles.push(filename);
|
|
3141
|
+
existingLocalVersions.add(migration.version);
|
|
3124
3142
|
}
|
|
3125
3143
|
if (json) {
|
|
3126
3144
|
outputJson({
|
|
@@ -4445,7 +4463,10 @@ function registerSchedulesGetCommand(schedulesCmd2) {
|
|
|
4445
4463
|
|
|
4446
4464
|
// src/commands/schedules/create.ts
|
|
4447
4465
|
function registerSchedulesCreateCommand(schedulesCmd2) {
|
|
4448
|
-
schedulesCmd2.command("create").description("Create a new schedule").requiredOption("--name <name>", "Schedule name").requiredOption(
|
|
4466
|
+
schedulesCmd2.command("create").description("Create a new schedule").requiredOption("--name <name>", "Schedule name").requiredOption(
|
|
4467
|
+
"--cron <expression>",
|
|
4468
|
+
'Cron expression. 5-field cron (e.g. "*/5 * * * *", "0 9 * * 1-5") or pg_cron interval syntax for sub-minute cadence (e.g. "30 seconds", "5 minutes").'
|
|
4469
|
+
).requiredOption("--url <url>", "URL to invoke").requiredOption("--method <method>", "HTTP method (GET, POST, PUT, PATCH, DELETE)").option("--headers <json>", "HTTP headers as JSON").option("--body <json>", "Request body as JSON").action(async (opts, cmd) => {
|
|
4449
4470
|
const { json } = getRootOpts(cmd);
|
|
4450
4471
|
try {
|
|
4451
4472
|
await requireAuth();
|
|
@@ -4489,7 +4510,10 @@ function registerSchedulesCreateCommand(schedulesCmd2) {
|
|
|
4489
4510
|
|
|
4490
4511
|
// src/commands/schedules/update.ts
|
|
4491
4512
|
function registerSchedulesUpdateCommand(schedulesCmd2) {
|
|
4492
|
-
schedulesCmd2.command("update <id>").description("Update a schedule").option("--name <name>", "New schedule name").option(
|
|
4513
|
+
schedulesCmd2.command("update <id>").description("Update a schedule").option("--name <name>", "New schedule name").option(
|
|
4514
|
+
"--cron <expression>",
|
|
4515
|
+
'New cron expression. 5-field cron or pg_cron interval syntax (e.g. "30 seconds").'
|
|
4516
|
+
).option("--url <url>", "New URL to invoke").option("--method <method>", "New HTTP method").option("--headers <json>", "New HTTP headers as JSON").option("--body <json>", "New request body as JSON").option("--active <bool>", "Enable/disable schedule (true/false)").action(async (id, opts, cmd) => {
|
|
4493
4517
|
const { json } = getRootOpts(cmd);
|
|
4494
4518
|
try {
|
|
4495
4519
|
await requireAuth();
|
|
@@ -4665,48 +4689,6 @@ function registerComputeGetCommand(computeCmd2) {
|
|
|
4665
4689
|
});
|
|
4666
4690
|
}
|
|
4667
4691
|
|
|
4668
|
-
// src/commands/compute/create.ts
|
|
4669
|
-
function registerComputeCreateCommand(computeCmd2) {
|
|
4670
|
-
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) => {
|
|
4671
|
-
const { json } = getRootOpts(cmd);
|
|
4672
|
-
try {
|
|
4673
|
-
await requireAuth();
|
|
4674
|
-
const body = {
|
|
4675
|
-
name: opts.name,
|
|
4676
|
-
imageUrl: opts.image,
|
|
4677
|
-
port: Number(opts.port),
|
|
4678
|
-
cpu: opts.cpu,
|
|
4679
|
-
memory: Number(opts.memory),
|
|
4680
|
-
region: opts.region
|
|
4681
|
-
};
|
|
4682
|
-
if (opts.env) {
|
|
4683
|
-
try {
|
|
4684
|
-
body.envVars = JSON.parse(opts.env);
|
|
4685
|
-
} catch {
|
|
4686
|
-
throw new CLIError("Invalid JSON for --env");
|
|
4687
|
-
}
|
|
4688
|
-
}
|
|
4689
|
-
const res = await ossFetch("/api/compute/services", {
|
|
4690
|
-
method: "POST",
|
|
4691
|
-
body: JSON.stringify(body)
|
|
4692
|
-
});
|
|
4693
|
-
const service = await res.json();
|
|
4694
|
-
if (json) {
|
|
4695
|
-
outputJson(service);
|
|
4696
|
-
} else {
|
|
4697
|
-
outputSuccess(`Service "${service.name}" created [${service.status}]`);
|
|
4698
|
-
if (service.endpointUrl) {
|
|
4699
|
-
console.log(` Endpoint: ${service.endpointUrl}`);
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
await reportCliUsage("cli.compute.create", true);
|
|
4703
|
-
} catch (err) {
|
|
4704
|
-
await reportCliUsage("cli.compute.create", false);
|
|
4705
|
-
handleError(err, json);
|
|
4706
|
-
}
|
|
4707
|
-
});
|
|
4708
|
-
}
|
|
4709
|
-
|
|
4710
4692
|
// src/commands/compute/update.ts
|
|
4711
4693
|
function registerComputeUpdateCommand(computeCmd2) {
|
|
4712
4694
|
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) => {
|
|
@@ -4862,175 +4844,295 @@ function registerComputeLogsCommand(computeCmd2) {
|
|
|
4862
4844
|
}
|
|
4863
4845
|
|
|
4864
4846
|
// src/commands/compute/deploy.ts
|
|
4865
|
-
import { existsSync as
|
|
4847
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4848
|
+
import { join as join12, resolve as resolve4 } from "path";
|
|
4849
|
+
|
|
4850
|
+
// src/lib/flyctl.ts
|
|
4851
|
+
import { spawn, spawnSync } from "child_process";
|
|
4852
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
4866
4853
|
import { join as join11 } from "path";
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
const memoryMatch = content.match(/memory\s*=\s*'(\d+)/);
|
|
4878
|
-
if (memoryMatch) config.memory = memoryMatch[1];
|
|
4879
|
-
const cpuKindMatch = content.match(/cpu_kind\s*=\s*'([^']+)'/);
|
|
4880
|
-
if (cpuKindMatch) config.cpuKind = cpuKindMatch[1];
|
|
4881
|
-
const cpusMatch = content.match(/cpus\s*=\s*(\d+)/);
|
|
4882
|
-
if (cpusMatch) config.cpus = Number(cpusMatch[1]);
|
|
4883
|
-
return config;
|
|
4854
|
+
function ensureFlyctlAvailable() {
|
|
4855
|
+
const r = spawnSync("flyctl", ["version"], {
|
|
4856
|
+
encoding: "utf8",
|
|
4857
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4858
|
+
});
|
|
4859
|
+
if (r.error || r.status !== 0) {
|
|
4860
|
+
throw new CLIError(
|
|
4861
|
+
"flyctl is required for source-mode deploy.\n \u2022 Install: curl -L https://fly.io/install.sh | sh\n \u2022 Or use --image <pre-built-image> instead.\n" + (r.stderr ? ` Detail: ${r.stderr.trim().slice(0, 200)}` : "")
|
|
4862
|
+
);
|
|
4863
|
+
}
|
|
4884
4864
|
}
|
|
4885
|
-
function
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
const cpuCount = parseInt(cpuParts[1] ?? "1", 10);
|
|
4895
|
-
return `# Auto-generated by InsForge CLI
|
|
4896
|
-
app = '${appName}'
|
|
4897
|
-
primary_region = '${opts.region}'
|
|
4865
|
+
function ensureFlyTomlStub(opts) {
|
|
4866
|
+
const path5 = join11(opts.dir, "fly.toml");
|
|
4867
|
+
if (existsSync7(path5)) {
|
|
4868
|
+
return () => {
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
const stub = `# Auto-generated by @insforge/cli for compute deploy. Safe to delete.
|
|
4872
|
+
app = "${opts.appId}"
|
|
4873
|
+
primary_region = "${opts.region}"
|
|
4898
4874
|
|
|
4899
4875
|
[build]
|
|
4900
|
-
dockerfile = 'Dockerfile'
|
|
4901
4876
|
|
|
4902
4877
|
[http_service]
|
|
4903
4878
|
internal_port = ${opts.port}
|
|
4904
4879
|
force_https = true
|
|
4905
|
-
auto_stop_machines =
|
|
4880
|
+
auto_stop_machines = false
|
|
4906
4881
|
auto_start_machines = true
|
|
4907
4882
|
min_machines_running = 0
|
|
4908
|
-
|
|
4909
|
-
[[vm]]
|
|
4910
|
-
memory = '${opts.memory}mb'
|
|
4911
|
-
cpu_kind = '${cpuKind}'
|
|
4912
|
-
cpus = ${cpuCount}
|
|
4913
4883
|
`;
|
|
4884
|
+
writeFileSync5(path5, stub, "utf8");
|
|
4885
|
+
return () => {
|
|
4886
|
+
try {
|
|
4887
|
+
unlinkSync2(path5);
|
|
4888
|
+
} catch {
|
|
4889
|
+
}
|
|
4890
|
+
};
|
|
4914
4891
|
}
|
|
4915
|
-
function
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4892
|
+
function flyctlBuildAndPush(opts) {
|
|
4893
|
+
return new Promise((resolve5, reject) => {
|
|
4894
|
+
const cleanupStub = ensureFlyTomlStub({
|
|
4895
|
+
dir: opts.dir,
|
|
4896
|
+
appId: opts.appId,
|
|
4897
|
+
region: opts.region,
|
|
4898
|
+
port: opts.port
|
|
4899
|
+
});
|
|
4900
|
+
const child = spawn(
|
|
4901
|
+
"flyctl",
|
|
4902
|
+
[
|
|
4903
|
+
"deploy",
|
|
4904
|
+
"--remote-only",
|
|
4905
|
+
"--build-only",
|
|
4906
|
+
"--push",
|
|
4907
|
+
"--app",
|
|
4908
|
+
opts.appId,
|
|
4909
|
+
"--image-label",
|
|
4910
|
+
opts.imageLabel,
|
|
4911
|
+
"--no-cache"
|
|
4912
|
+
],
|
|
4913
|
+
{
|
|
4914
|
+
cwd: opts.dir,
|
|
4915
|
+
env: { ...process.env, FLY_API_TOKEN: opts.token },
|
|
4916
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
4917
|
+
}
|
|
4929
4918
|
);
|
|
4930
|
-
|
|
4931
|
-
|
|
4919
|
+
let captured = "";
|
|
4920
|
+
child.stdout?.on("data", (b) => {
|
|
4921
|
+
const s = b.toString();
|
|
4922
|
+
captured += s;
|
|
4923
|
+
process.stdout.write(s);
|
|
4924
|
+
});
|
|
4925
|
+
child.stderr?.on("data", (b) => {
|
|
4926
|
+
const s = b.toString();
|
|
4927
|
+
captured += s;
|
|
4928
|
+
process.stderr.write(s);
|
|
4929
|
+
});
|
|
4930
|
+
child.on("error", (err) => {
|
|
4931
|
+
cleanupStub();
|
|
4932
|
+
reject(new CLIError(`flyctl deploy could not start: ${err.message}`));
|
|
4933
|
+
});
|
|
4934
|
+
child.on("exit", (code) => {
|
|
4935
|
+
cleanupStub();
|
|
4936
|
+
if (code !== 0) {
|
|
4937
|
+
return reject(
|
|
4938
|
+
new CLIError(`flyctl deploy --build-only failed (exit ${code}). See output above.`)
|
|
4939
|
+
);
|
|
4940
|
+
}
|
|
4941
|
+
const m = captured.match(/pushing manifest for registry\.fly\.io\/[^\s]+@(sha256:[0-9a-f]+)/);
|
|
4942
|
+
if (!m) {
|
|
4943
|
+
return reject(
|
|
4944
|
+
new CLIError(
|
|
4945
|
+
'flyctl deploy succeeded but the buildkit "pushing manifest" line was not found. Cannot determine image digest \u2014 please re-run with FLY_LOG_LEVEL=debug and report.'
|
|
4946
|
+
)
|
|
4947
|
+
);
|
|
4948
|
+
}
|
|
4949
|
+
resolve5({ imageRef: `registry.fly.io/${opts.appId}@${m[1]}` });
|
|
4950
|
+
});
|
|
4951
|
+
});
|
|
4932
4952
|
}
|
|
4953
|
+
|
|
4954
|
+
// src/commands/compute/deploy.ts
|
|
4933
4955
|
function registerComputeDeployCommand(computeCmd2) {
|
|
4934
|
-
computeCmd2.command("deploy [
|
|
4956
|
+
computeCmd2.command("deploy [dir]").description(
|
|
4957
|
+
"Deploy a compute service. Two modes:\n compute deploy <dir> --name <name> (source mode \u2014 flyctl remote build + push, requires flyctl on PATH; no Docker needed)\n compute deploy --image <url> --name <name> (image mode \u2014 deploys pre-built image, no flyctl/Docker required)"
|
|
4958
|
+
).requiredOption("--name <name>", "Service name (DNS-safe, e.g. my-api)").option("--image <url>", "Pre-built image URL (image mode)").option("--port <port>", "Container port", "8080").option(
|
|
4959
|
+
"--cpu <tier>",
|
|
4960
|
+
"CPU tier in <kind>-<N>x format (e.g. shared-1x, performance-2x)",
|
|
4961
|
+
"shared-1x"
|
|
4962
|
+
).option("--memory <mb>", "Memory in MB", "512").option("--region <region>", "Fly.io region", "iad").option("--env <json>", "Env vars as JSON object").action(async (dir, opts, cmd) => {
|
|
4935
4963
|
const { json } = getRootOpts(cmd);
|
|
4936
4964
|
try {
|
|
4937
4965
|
await requireAuth();
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
const port = Number(opts.port)
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4966
|
+
if (dir && opts.image) {
|
|
4967
|
+
throw new CLIError("Cannot use both [dir] and --image \u2014 pick one mode.");
|
|
4968
|
+
}
|
|
4969
|
+
if (!dir && !opts.image) {
|
|
4970
|
+
throw new CLIError(
|
|
4971
|
+
"Must provide either [dir] (source mode) or --image <url> (image mode)."
|
|
4972
|
+
);
|
|
4973
|
+
}
|
|
4974
|
+
const port = Number(opts.port);
|
|
4975
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
4976
|
+
throw new CLIError(`Invalid --port: ${opts.port}`);
|
|
4977
|
+
}
|
|
4978
|
+
const memory = Number(opts.memory);
|
|
4979
|
+
if (!Number.isInteger(memory) || memory <= 0) {
|
|
4980
|
+
throw new CLIError(`Invalid --memory: ${opts.memory}`);
|
|
4981
|
+
}
|
|
4950
4982
|
let envVars;
|
|
4951
4983
|
if (opts.env) {
|
|
4984
|
+
let parsed;
|
|
4952
4985
|
try {
|
|
4953
|
-
|
|
4986
|
+
parsed = JSON.parse(opts.env);
|
|
4954
4987
|
} catch {
|
|
4955
4988
|
throw new CLIError("Invalid JSON for --env");
|
|
4956
4989
|
}
|
|
4990
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4991
|
+
throw new CLIError('--env must be a JSON object like {"KEY":"value"}');
|
|
4992
|
+
}
|
|
4993
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
4994
|
+
if (typeof v !== "string") {
|
|
4995
|
+
throw new CLIError(
|
|
4996
|
+
`--env values must be strings \u2014 got ${typeof v} for key "${k}"`
|
|
4997
|
+
);
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
envVars = parsed;
|
|
5001
|
+
}
|
|
5002
|
+
const baseBody = {
|
|
5003
|
+
name: opts.name,
|
|
5004
|
+
port,
|
|
5005
|
+
cpu: opts.cpu,
|
|
5006
|
+
memory,
|
|
5007
|
+
region: opts.region
|
|
5008
|
+
};
|
|
5009
|
+
if (envVars) baseBody.envVars = envVars;
|
|
5010
|
+
if (!dir) {
|
|
5011
|
+
const body = { ...baseBody, imageUrl: opts.image };
|
|
5012
|
+
const listRes2 = await ossFetch("/api/compute/services");
|
|
5013
|
+
const existing2 = (await listRes2.json()).find(
|
|
5014
|
+
(s) => s.name === opts.name
|
|
5015
|
+
);
|
|
5016
|
+
let res;
|
|
5017
|
+
if (existing2) {
|
|
5018
|
+
if (!json) outputInfo(`Found existing service "${opts.name}", updating...`);
|
|
5019
|
+
const updateBody2 = { ...body };
|
|
5020
|
+
delete updateBody2.name;
|
|
5021
|
+
res = await ossFetch(`/api/compute/services/${encodeURIComponent(existing2.id)}`, {
|
|
5022
|
+
method: "PATCH",
|
|
5023
|
+
body: JSON.stringify(updateBody2)
|
|
5024
|
+
});
|
|
5025
|
+
} else {
|
|
5026
|
+
res = await ossFetch("/api/compute/services", {
|
|
5027
|
+
method: "POST",
|
|
5028
|
+
body: JSON.stringify(body)
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
5031
|
+
const service2 = await res.json();
|
|
5032
|
+
if (json) {
|
|
5033
|
+
outputJson(service2);
|
|
5034
|
+
} else {
|
|
5035
|
+
const verb = existing2 ? "updated" : "deployed";
|
|
5036
|
+
outputSuccess(`Service "${service2.name}" ${verb} [${service2.status}]`);
|
|
5037
|
+
if (service2.endpointUrl) console.log(` Endpoint: ${service2.endpointUrl}`);
|
|
5038
|
+
}
|
|
5039
|
+
await reportCliUsage("cli.compute.deploy", true);
|
|
5040
|
+
return;
|
|
5041
|
+
}
|
|
5042
|
+
const absDir = resolve4(dir);
|
|
5043
|
+
const dockerfilePath = join12(absDir, "Dockerfile");
|
|
5044
|
+
if (!existsSync8(dockerfilePath)) {
|
|
5045
|
+
throw new CLIError(
|
|
5046
|
+
`No Dockerfile at ${dockerfilePath}.
|
|
5047
|
+
Either:
|
|
5048
|
+
\u2022 Create one (ask your AI agent \u2014 see the insforge-cli skill)
|
|
5049
|
+
\u2022 Use --image <url> to deploy a pre-built image instead`
|
|
5050
|
+
);
|
|
4957
5051
|
}
|
|
4958
|
-
|
|
5052
|
+
ensureFlyctlAvailable();
|
|
5053
|
+
if (!json) outputInfo(`Detected Dockerfile at ${dockerfilePath}`);
|
|
4959
5054
|
const listRes = await ossFetch("/api/compute/services");
|
|
4960
|
-
const
|
|
4961
|
-
const existing = services.find((s) => s.name === opts.name);
|
|
5055
|
+
const existing = (await listRes.json()).find((s) => s.name === opts.name);
|
|
4962
5056
|
let serviceId;
|
|
4963
5057
|
let flyAppId;
|
|
4964
5058
|
if (existing) {
|
|
5059
|
+
if (!existing.flyAppId) {
|
|
5060
|
+
throw new CLIError(
|
|
5061
|
+
`Service "${opts.name}" exists but has no Fly app yet. Delete it and redeploy.`
|
|
5062
|
+
);
|
|
5063
|
+
}
|
|
4965
5064
|
serviceId = existing.id;
|
|
4966
5065
|
flyAppId = existing.flyAppId;
|
|
4967
|
-
if (!json) outputInfo(`Found existing service,
|
|
5066
|
+
if (!json) outputInfo(`Found existing service "${opts.name}" (${flyAppId}), updating...`);
|
|
4968
5067
|
} else {
|
|
4969
5068
|
if (!json) outputInfo(`Creating service "${opts.name}"...`);
|
|
4970
|
-
const
|
|
4971
|
-
name: opts.name,
|
|
4972
|
-
imageUrl: "dockerfile",
|
|
4973
|
-
port,
|
|
4974
|
-
cpu,
|
|
4975
|
-
memory,
|
|
4976
|
-
region
|
|
4977
|
-
};
|
|
4978
|
-
if (envVars) body.envVars = envVars;
|
|
4979
|
-
const deployRes = await ossFetch("/api/compute/services/deploy", {
|
|
5069
|
+
const prepareRes = await ossFetch("/api/compute/services/deploy", {
|
|
4980
5070
|
method: "POST",
|
|
4981
|
-
body: JSON.stringify(
|
|
5071
|
+
body: JSON.stringify(baseBody)
|
|
4982
5072
|
});
|
|
4983
|
-
const
|
|
4984
|
-
serviceId =
|
|
4985
|
-
flyAppId =
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
const
|
|
4995
|
-
|
|
5073
|
+
const prepared = await prepareRes.json();
|
|
5074
|
+
serviceId = prepared.id;
|
|
5075
|
+
flyAppId = prepared.flyAppId;
|
|
5076
|
+
if (!json) outputInfo(`Created Fly app ${flyAppId}`);
|
|
5077
|
+
}
|
|
5078
|
+
if (!json) outputInfo("Requesting deploy token...");
|
|
5079
|
+
const tokenRes = await ossFetch(
|
|
5080
|
+
`/api/compute/services/${encodeURIComponent(serviceId)}/deploy-token`,
|
|
5081
|
+
{ method: "POST" }
|
|
5082
|
+
);
|
|
5083
|
+
const tokenJson = await tokenRes.json();
|
|
5084
|
+
const imageLabel = `cli-${Date.now()}`;
|
|
5085
|
+
if (!json) outputInfo(`Building & pushing on Fly remote builder...`);
|
|
5086
|
+
let imageRef;
|
|
4996
5087
|
try {
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
} else {
|
|
5019
|
-
outputSuccess(`Service "${synced.name}" deployed [${synced.status}]`);
|
|
5020
|
-
if (synced.endpointUrl) {
|
|
5021
|
-
console.log(` Endpoint: ${synced.endpointUrl}`);
|
|
5022
|
-
}
|
|
5023
|
-
}
|
|
5024
|
-
await reportCliUsage("cli.compute.deploy", true);
|
|
5025
|
-
} finally {
|
|
5026
|
-
try {
|
|
5027
|
-
unlinkSync2(existingTomlPath);
|
|
5028
|
-
if (hadExistingToml) {
|
|
5029
|
-
renameSync(backupTomlPath, existingTomlPath);
|
|
5088
|
+
({ imageRef } = await flyctlBuildAndPush({
|
|
5089
|
+
dir: absDir,
|
|
5090
|
+
appId: flyAppId,
|
|
5091
|
+
imageLabel,
|
|
5092
|
+
token: tokenJson.token,
|
|
5093
|
+
region: opts.region,
|
|
5094
|
+
port
|
|
5095
|
+
}));
|
|
5096
|
+
} catch (buildErr) {
|
|
5097
|
+
if (!existing) {
|
|
5098
|
+
try {
|
|
5099
|
+
await ossFetch(`/api/compute/services/${encodeURIComponent(serviceId)}`, {
|
|
5100
|
+
method: "DELETE"
|
|
5101
|
+
});
|
|
5102
|
+
if (!json) outputInfo(`Rolled back service "${opts.name}" after build failure.`);
|
|
5103
|
+
} catch {
|
|
5104
|
+
if (!json) {
|
|
5105
|
+
outputInfo(
|
|
5106
|
+
`Build failed and rollback also failed. Run: npx @insforge/cli compute delete ${serviceId}`
|
|
5107
|
+
);
|
|
5108
|
+
}
|
|
5030
5109
|
}
|
|
5031
|
-
} catch {
|
|
5032
5110
|
}
|
|
5111
|
+
throw buildErr;
|
|
5112
|
+
}
|
|
5113
|
+
if (!json) outputInfo("Launching machine...");
|
|
5114
|
+
const updateBody = {
|
|
5115
|
+
imageUrl: imageRef,
|
|
5116
|
+
port,
|
|
5117
|
+
cpu: opts.cpu,
|
|
5118
|
+
memory,
|
|
5119
|
+
region: opts.region
|
|
5120
|
+
};
|
|
5121
|
+
if (envVars) updateBody.envVars = envVars;
|
|
5122
|
+
const finalRes = await ossFetch(
|
|
5123
|
+
`/api/compute/services/${encodeURIComponent(serviceId)}`,
|
|
5124
|
+
{ method: "PATCH", body: JSON.stringify(updateBody) }
|
|
5125
|
+
);
|
|
5126
|
+
const service = await finalRes.json();
|
|
5127
|
+
if (json) {
|
|
5128
|
+
outputJson(service);
|
|
5129
|
+
} else {
|
|
5130
|
+
const verb = existing ? "updated" : "deployed";
|
|
5131
|
+
outputSuccess(`Service "${service.name}" ${verb} [${service.status}]`);
|
|
5132
|
+
if (service.endpointUrl) console.log(` Endpoint: ${service.endpointUrl}`);
|
|
5133
|
+
console.log(` Image: ${imageRef} (built remotely; no local image to clean up)`);
|
|
5033
5134
|
}
|
|
5135
|
+
await reportCliUsage("cli.compute.deploy", true);
|
|
5034
5136
|
} catch (err) {
|
|
5035
5137
|
await reportCliUsage("cli.compute.deploy", false);
|
|
5036
5138
|
handleError(err, json);
|
|
@@ -5717,7 +5819,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
5717
5819
|
const s = !json ? clack10.spinner() : null;
|
|
5718
5820
|
s?.start("Collecting diagnostic data...");
|
|
5719
5821
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
5720
|
-
const cliVersion = "0.1.
|
|
5822
|
+
const cliVersion = "0.1.61";
|
|
5721
5823
|
s?.stop("Data collected");
|
|
5722
5824
|
if (!json) {
|
|
5723
5825
|
console.log(`
|
|
@@ -5946,7 +6048,7 @@ function formatBytesCompact(bytes) {
|
|
|
5946
6048
|
|
|
5947
6049
|
// src/index.ts
|
|
5948
6050
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5949
|
-
var pkg = JSON.parse(
|
|
6051
|
+
var pkg = JSON.parse(readFileSync7(join13(__dirname, "../package.json"), "utf-8"));
|
|
5950
6052
|
var INSFORGE_LOGO = `
|
|
5951
6053
|
\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
|
|
5952
6054
|
\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
|
|
@@ -6018,7 +6120,6 @@ registerDiagnoseCommands(diagnoseCmd);
|
|
|
6018
6120
|
var computeCmd = program.command("compute").description("Manage compute services (Docker containers on Fly.io)");
|
|
6019
6121
|
registerComputeListCommand(computeCmd);
|
|
6020
6122
|
registerComputeGetCommand(computeCmd);
|
|
6021
|
-
registerComputeCreateCommand(computeCmd);
|
|
6022
6123
|
registerComputeDeployCommand(computeCmd);
|
|
6023
6124
|
registerComputeUpdateCommand(computeCmd);
|
|
6024
6125
|
registerComputeDeleteCommand(computeCmd);
|