@insforge/cli 0.1.57 → 0.1.60
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 +367 -209
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
5
5
|
import { join as join12, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -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) => {
|
|
@@ -539,6 +539,9 @@ To sign in, open this URL in your browser:
|
|
|
539
539
|
|
|
540
540
|
// src/lib/credentials.ts
|
|
541
541
|
import * as clack3 from "@clack/prompts";
|
|
542
|
+
function isPatLogin(creds) {
|
|
543
|
+
return creds?.refresh_token?.startsWith("uak_") ?? false;
|
|
544
|
+
}
|
|
542
545
|
async function requireAuth(apiUrl, allowOssBypass = true) {
|
|
543
546
|
const projConfig = getProjectConfig();
|
|
544
547
|
if (allowOssBypass && projConfig?.project_id === FAKE_PROJECT_ID) {
|
|
@@ -556,6 +559,10 @@ async function requireAuth(apiUrl, allowOssBypass = true) {
|
|
|
556
559
|
}
|
|
557
560
|
const creds = getCredentials();
|
|
558
561
|
if (creds && creds.access_token) return creds;
|
|
562
|
+
if (isPatLogin(creds)) {
|
|
563
|
+
await refreshAccessToken(apiUrl);
|
|
564
|
+
return getCredentials();
|
|
565
|
+
}
|
|
559
566
|
clack3.log.info("You need to log in to continue.");
|
|
560
567
|
for (; ; ) {
|
|
561
568
|
try {
|
|
@@ -573,10 +580,43 @@ async function requireAuth(apiUrl, allowOssBypass = true) {
|
|
|
573
580
|
}
|
|
574
581
|
async function refreshAccessToken(apiUrl) {
|
|
575
582
|
const creds = getCredentials();
|
|
576
|
-
if (!creds
|
|
577
|
-
throw new AuthError("
|
|
583
|
+
if (!creds) {
|
|
584
|
+
throw new AuthError("Not logged in. Run `npx @insforge/cli login` first.");
|
|
578
585
|
}
|
|
579
586
|
const platformUrl = getPlatformApiUrl(apiUrl);
|
|
587
|
+
if (isPatLogin(creds)) {
|
|
588
|
+
let res;
|
|
589
|
+
try {
|
|
590
|
+
res = await fetch(`${platformUrl}/auth/v1/exchange-api-key`, {
|
|
591
|
+
method: "POST",
|
|
592
|
+
headers: { "Content-Type": "application/json" },
|
|
593
|
+
body: JSON.stringify({ apiKey: creds.refresh_token })
|
|
594
|
+
});
|
|
595
|
+
} catch {
|
|
596
|
+
throw new AuthError(
|
|
597
|
+
`Unable to reach auth server at ${platformUrl}. Check your network connection.`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
if (!res.ok) {
|
|
601
|
+
if (res.status === 401 || res.status === 403 || res.status === 404) {
|
|
602
|
+
throw new AuthError(
|
|
603
|
+
"API key is invalid or revoked. Run `npx @insforge/cli login --user-api-key <new-key>` again."
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
throw new AuthError(
|
|
607
|
+
`Auth server returned HTTP ${res.status} while refreshing session. Please retry shortly.`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const data = await res.json().catch(() => ({}));
|
|
611
|
+
if (typeof data.token !== "string" || data.token.length === 0) {
|
|
612
|
+
throw new AuthError("Exchange endpoint returned an invalid response (missing token).");
|
|
613
|
+
}
|
|
614
|
+
saveCredentials({ ...creds, access_token: data.token });
|
|
615
|
+
return data.token;
|
|
616
|
+
}
|
|
617
|
+
if (!creds.refresh_token) {
|
|
618
|
+
throw new AuthError("Refresh token not found. Run `npx @insforge/cli login` again.");
|
|
619
|
+
}
|
|
580
620
|
const config = getGlobalConfig();
|
|
581
621
|
const clientId = config.oauth_client_id ?? DEFAULT_CLIENT_ID;
|
|
582
622
|
try {
|
|
@@ -708,7 +748,7 @@ async function reportAgentConnected(payload, apiUrl) {
|
|
|
708
748
|
await fetch(`${baseUrl}/tracking/v1/agent-connected`, {
|
|
709
749
|
method: "POST",
|
|
710
750
|
headers,
|
|
711
|
-
body: JSON.stringify(payload)
|
|
751
|
+
body: JSON.stringify({ ...payload, client: "cli" })
|
|
712
752
|
});
|
|
713
753
|
}
|
|
714
754
|
async function streamDiagnosticAnalysis(payload, onEvent, apiUrl) {
|
|
@@ -774,10 +814,12 @@ async function createProject(orgId, name, region, apiUrl) {
|
|
|
774
814
|
|
|
775
815
|
// src/commands/login.ts
|
|
776
816
|
function registerLoginCommand(program2) {
|
|
777
|
-
program2.command("login").description("Authenticate with InsForge platform").option("--email", "Login with email and password instead of browser").option("--client-id <id>", "OAuth client ID (defaults to insforge-cli)").action(async (opts, cmd) => {
|
|
817
|
+
program2.command("login").description("Authenticate with InsForge platform").option("--email", "Login with email and password instead of browser").option("--client-id <id>", "OAuth client ID (defaults to insforge-cli)").option("--user-api-key <key>", "Authenticate with a uak_ personal access token").action(async (opts, cmd) => {
|
|
778
818
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
779
819
|
try {
|
|
780
|
-
if (opts.
|
|
820
|
+
if (opts.userApiKey) {
|
|
821
|
+
await loginWithUserApiKey(opts.userApiKey, json, apiUrl);
|
|
822
|
+
} else if (opts.email) {
|
|
781
823
|
await loginWithEmail(json, apiUrl);
|
|
782
824
|
} else {
|
|
783
825
|
await loginWithOAuth(json, apiUrl);
|
|
@@ -846,6 +888,78 @@ async function loginWithOAuth(json, apiUrl) {
|
|
|
846
888
|
console.log(JSON.stringify({ success: true, user: creds.user }));
|
|
847
889
|
}
|
|
848
890
|
}
|
|
891
|
+
async function loginWithUserApiKey(key, json, apiUrl) {
|
|
892
|
+
if (!json) {
|
|
893
|
+
clack4.intro("InsForge CLI");
|
|
894
|
+
}
|
|
895
|
+
if (!key.startsWith("uak_")) {
|
|
896
|
+
throw new CLIError('Invalid API key \u2014 must start with "uak_".');
|
|
897
|
+
}
|
|
898
|
+
const s = !json ? clack4.spinner() : null;
|
|
899
|
+
s?.start("Verifying API key...");
|
|
900
|
+
let jwt;
|
|
901
|
+
let user;
|
|
902
|
+
try {
|
|
903
|
+
const exchanged = await exchangePatForJwt(key, apiUrl);
|
|
904
|
+
jwt = exchanged.token;
|
|
905
|
+
user = exchanged.user;
|
|
906
|
+
} catch (err) {
|
|
907
|
+
s?.stop("API key verification failed");
|
|
908
|
+
throw err instanceof CLIError ? err : new CLIError(err instanceof Error ? err.message : String(err));
|
|
909
|
+
}
|
|
910
|
+
saveCredentials({
|
|
911
|
+
access_token: jwt,
|
|
912
|
+
refresh_token: key,
|
|
913
|
+
user
|
|
914
|
+
});
|
|
915
|
+
if (!json) {
|
|
916
|
+
s?.stop(`Authenticated as ${user.email}`);
|
|
917
|
+
clack4.outro("Done");
|
|
918
|
+
} else {
|
|
919
|
+
console.log(JSON.stringify({ success: true, user }));
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
async function exchangePatForJwt(apiKey, apiUrl) {
|
|
923
|
+
const baseUrl = getPlatformApiUrl(apiUrl);
|
|
924
|
+
const fullUrl = `${baseUrl}/auth/v1/exchange-api-key`;
|
|
925
|
+
let res;
|
|
926
|
+
try {
|
|
927
|
+
res = await fetch(fullUrl, {
|
|
928
|
+
method: "POST",
|
|
929
|
+
headers: { "Content-Type": "application/json" },
|
|
930
|
+
body: JSON.stringify({ apiKey })
|
|
931
|
+
});
|
|
932
|
+
} catch (err) {
|
|
933
|
+
throw new CLIError(formatFetchError(err, fullUrl));
|
|
934
|
+
}
|
|
935
|
+
if (!res.ok) {
|
|
936
|
+
const body = await res.json().catch(() => ({}));
|
|
937
|
+
const msg = body.message ?? body.error ?? `HTTP ${res.status}`;
|
|
938
|
+
throw new CLIError(`API key is invalid or revoked: ${msg}`);
|
|
939
|
+
}
|
|
940
|
+
const data = await res.json().catch(() => ({}));
|
|
941
|
+
if (typeof data.token !== "string" || data.token.length === 0) {
|
|
942
|
+
throw new CLIError("Exchange endpoint returned an invalid response (missing token).");
|
|
943
|
+
}
|
|
944
|
+
const jwt = data.token;
|
|
945
|
+
let profileRes;
|
|
946
|
+
try {
|
|
947
|
+
profileRes = await fetch(`${baseUrl}/auth/v1/profile`, {
|
|
948
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
949
|
+
});
|
|
950
|
+
} catch (err) {
|
|
951
|
+
throw new CLIError(formatFetchError(err, `${baseUrl}/auth/v1/profile`));
|
|
952
|
+
}
|
|
953
|
+
if (!profileRes.ok) {
|
|
954
|
+
throw new CLIError(`Exchange succeeded but could not fetch profile: HTTP ${profileRes.status}`);
|
|
955
|
+
}
|
|
956
|
+
const profile = await profileRes.json().catch(() => null);
|
|
957
|
+
const user = profile && typeof profile === "object" && "user" in profile ? profile.user : profile ?? void 0;
|
|
958
|
+
if (!user) {
|
|
959
|
+
throw new CLIError("Exchange succeeded but profile response was empty");
|
|
960
|
+
}
|
|
961
|
+
return { token: jwt, user };
|
|
962
|
+
}
|
|
849
963
|
|
|
850
964
|
// src/lib/output.ts
|
|
851
965
|
import Table from "cli-table3";
|
|
@@ -1208,10 +1322,11 @@ async function ossFetch(path5, options = {}) {
|
|
|
1208
1322
|
message += `
|
|
1209
1323
|
${err.nextActions}`;
|
|
1210
1324
|
}
|
|
1211
|
-
|
|
1325
|
+
const isRouteLevel404 = !err.error || err.error === "NOT_FOUND";
|
|
1326
|
+
if (res.status === 404 && isRouteLevel404 && path5.startsWith("/api/compute")) {
|
|
1212
1327
|
message = "Compute services are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin to enable compute.";
|
|
1213
1328
|
}
|
|
1214
|
-
if (res.status === 404 && path5 === "/api/database/migrations") {
|
|
1329
|
+
if (res.status === 404 && isRouteLevel404 && path5 === "/api/database/migrations") {
|
|
1215
1330
|
message = "Database migrations are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin about database migration support.";
|
|
1216
1331
|
}
|
|
1217
1332
|
throw new CLIError(message);
|
|
@@ -1351,11 +1466,11 @@ async function collectDeploymentFiles(sourceDir) {
|
|
|
1351
1466
|
return files;
|
|
1352
1467
|
}
|
|
1353
1468
|
async function createZipBuffer(sourceDir) {
|
|
1354
|
-
return new Promise((
|
|
1469
|
+
return new Promise((resolve5, reject) => {
|
|
1355
1470
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1356
1471
|
const chunks = [];
|
|
1357
1472
|
archive.on("data", (chunk) => chunks.push(chunk));
|
|
1358
|
-
archive.on("end", () =>
|
|
1473
|
+
archive.on("end", () => resolve5(Buffer.concat(chunks)));
|
|
1359
1474
|
archive.on("error", (err) => reject(err));
|
|
1360
1475
|
archive.directory(sourceDir, false, (entry) => {
|
|
1361
1476
|
if (shouldExclude(entry.name)) return false;
|
|
@@ -1437,7 +1552,7 @@ async function pollDeployment(deploymentId, spinner7, syncBeforeRead) {
|
|
|
1437
1552
|
const startTime = Date.now();
|
|
1438
1553
|
let deployment = null;
|
|
1439
1554
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1440
|
-
await new Promise((
|
|
1555
|
+
await new Promise((resolve5) => setTimeout(resolve5, POLL_INTERVAL_MS));
|
|
1441
1556
|
try {
|
|
1442
1557
|
if (syncBeforeRead) {
|
|
1443
1558
|
await ossFetch(`/api/deployments/${deploymentId}/sync`, { method: "POST" });
|
|
@@ -2754,13 +2869,17 @@ import { join as join8 } from "path";
|
|
|
2754
2869
|
// src/lib/migrations.ts
|
|
2755
2870
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2756
2871
|
import { join as join7 } from "path";
|
|
2757
|
-
var MIGRATION_VERSION_REGEX = /^\d{
|
|
2758
|
-
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;
|
|
2759
2874
|
function assertValidMigrationVersion(version) {
|
|
2760
2875
|
if (!MIGRATION_VERSION_REGEX.test(version)) {
|
|
2761
|
-
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).`);
|
|
2762
2877
|
}
|
|
2763
2878
|
}
|
|
2879
|
+
function canonicalMigrationVersion(version) {
|
|
2880
|
+
assertValidMigrationVersion(version);
|
|
2881
|
+
return BigInt(version).toString();
|
|
2882
|
+
}
|
|
2764
2883
|
function parseMigrationFilename(filename) {
|
|
2765
2884
|
const match = MIGRATION_FILENAME_REGEX.exec(filename);
|
|
2766
2885
|
if (!match) {
|
|
@@ -2768,11 +2887,16 @@ function parseMigrationFilename(filename) {
|
|
|
2768
2887
|
}
|
|
2769
2888
|
return {
|
|
2770
2889
|
filename,
|
|
2771
|
-
version: match[1],
|
|
2890
|
+
version: canonicalMigrationVersion(match[1]),
|
|
2772
2891
|
name: match[2]
|
|
2773
2892
|
};
|
|
2774
2893
|
}
|
|
2775
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
|
+
}
|
|
2776
2900
|
return left.localeCompare(right);
|
|
2777
2901
|
}
|
|
2778
2902
|
function getRemoteMigrationVersionStatus(version, appliedVersions, latestRemoteVersion) {
|
|
@@ -2794,6 +2918,10 @@ function formatMigrationVersion(date) {
|
|
|
2794
2918
|
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
2795
2919
|
}
|
|
2796
2920
|
function incrementMigrationVersion(version) {
|
|
2921
|
+
assertValidMigrationVersion(version);
|
|
2922
|
+
if (!/^\d{14}$/u.test(version)) {
|
|
2923
|
+
return String(BigInt(version) + 1n);
|
|
2924
|
+
}
|
|
2797
2925
|
const year = Number(version.slice(0, 4));
|
|
2798
2926
|
const month = Number(version.slice(4, 6)) - 1;
|
|
2799
2927
|
const day = Number(version.slice(6, 8));
|
|
@@ -2876,8 +3004,9 @@ function findOlderThanHeadLocalMigrations(migrations, appliedVersions, latestRem
|
|
|
2876
3004
|
);
|
|
2877
3005
|
}
|
|
2878
3006
|
function findLocalMigrationByVersion(version, filenames) {
|
|
3007
|
+
const canonicalVersion = canonicalMigrationVersion(version);
|
|
2879
3008
|
const matches = filenames.map((filename) => parseMigrationFilename(filename)).filter(
|
|
2880
|
-
(migration) => migration !== null && migration.version ===
|
|
3009
|
+
(migration) => migration !== null && migration.version === canonicalVersion
|
|
2881
3010
|
);
|
|
2882
3011
|
if (matches.length === 0) {
|
|
2883
3012
|
throw new CLIError(`Local migration for version ${version} not found.`);
|
|
@@ -2890,7 +3019,7 @@ function findLocalMigrationByVersion(version, filenames) {
|
|
|
2890
3019
|
return matches[0];
|
|
2891
3020
|
}
|
|
2892
3021
|
function resolveMigrationTarget(target, filenames) {
|
|
2893
|
-
if (/^\d{
|
|
3022
|
+
if (/^\d{1,64}$/u.test(target)) {
|
|
2894
3023
|
return findLocalMigrationByVersion(target, filenames);
|
|
2895
3024
|
}
|
|
2896
3025
|
const parsedTarget = parseMigrationFilename(target);
|
|
@@ -2929,7 +3058,7 @@ async function fetchRemoteMigrations() {
|
|
|
2929
3058
|
const raw = await res.json();
|
|
2930
3059
|
const migrations = Array.isArray(raw.migrations) ? raw.migrations : [];
|
|
2931
3060
|
for (const migration of migrations) {
|
|
2932
|
-
|
|
3061
|
+
migration.version = canonicalMigrationVersion(migration.version);
|
|
2933
3062
|
}
|
|
2934
3063
|
return migrations;
|
|
2935
3064
|
}
|
|
@@ -2989,6 +3118,9 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
2989
3118
|
await requireAuth();
|
|
2990
3119
|
const migrations = await fetchRemoteMigrations();
|
|
2991
3120
|
const migrationsDir = ensureMigrationsDir();
|
|
3121
|
+
const existingLocalVersions = new Set(
|
|
3122
|
+
listLocalMigrationFilenames().map((filename) => parseMigrationFilename(filename)).filter((migration) => migration !== null).map((migration) => migration.version)
|
|
3123
|
+
);
|
|
2992
3124
|
const createdFiles = [];
|
|
2993
3125
|
const skippedFiles = [];
|
|
2994
3126
|
for (const migration of [...migrations].sort(
|
|
@@ -3000,12 +3132,13 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
3000
3132
|
migration.name
|
|
3001
3133
|
);
|
|
3002
3134
|
const filePath = join8(migrationsDir, filename);
|
|
3003
|
-
if (existsSync4(filePath)) {
|
|
3135
|
+
if (existingLocalVersions.has(migration.version) || existsSync4(filePath)) {
|
|
3004
3136
|
skippedFiles.push(filename);
|
|
3005
3137
|
continue;
|
|
3006
3138
|
}
|
|
3007
3139
|
writeFileSync3(filePath, formatMigrationSql(migration.statements));
|
|
3008
3140
|
createdFiles.push(filename);
|
|
3141
|
+
existingLocalVersions.add(migration.version);
|
|
3009
3142
|
}
|
|
3010
3143
|
if (json) {
|
|
3011
3144
|
outputJson({
|
|
@@ -4330,7 +4463,10 @@ function registerSchedulesGetCommand(schedulesCmd2) {
|
|
|
4330
4463
|
|
|
4331
4464
|
// src/commands/schedules/create.ts
|
|
4332
4465
|
function registerSchedulesCreateCommand(schedulesCmd2) {
|
|
4333
|
-
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) => {
|
|
4334
4470
|
const { json } = getRootOpts(cmd);
|
|
4335
4471
|
try {
|
|
4336
4472
|
await requireAuth();
|
|
@@ -4374,7 +4510,10 @@ function registerSchedulesCreateCommand(schedulesCmd2) {
|
|
|
4374
4510
|
|
|
4375
4511
|
// src/commands/schedules/update.ts
|
|
4376
4512
|
function registerSchedulesUpdateCommand(schedulesCmd2) {
|
|
4377
|
-
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) => {
|
|
4378
4517
|
const { json } = getRootOpts(cmd);
|
|
4379
4518
|
try {
|
|
4380
4519
|
await requireAuth();
|
|
@@ -4550,48 +4689,6 @@ function registerComputeGetCommand(computeCmd2) {
|
|
|
4550
4689
|
});
|
|
4551
4690
|
}
|
|
4552
4691
|
|
|
4553
|
-
// src/commands/compute/create.ts
|
|
4554
|
-
function registerComputeCreateCommand(computeCmd2) {
|
|
4555
|
-
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) => {
|
|
4556
|
-
const { json } = getRootOpts(cmd);
|
|
4557
|
-
try {
|
|
4558
|
-
await requireAuth();
|
|
4559
|
-
const body = {
|
|
4560
|
-
name: opts.name,
|
|
4561
|
-
imageUrl: opts.image,
|
|
4562
|
-
port: Number(opts.port),
|
|
4563
|
-
cpu: opts.cpu,
|
|
4564
|
-
memory: Number(opts.memory),
|
|
4565
|
-
region: opts.region
|
|
4566
|
-
};
|
|
4567
|
-
if (opts.env) {
|
|
4568
|
-
try {
|
|
4569
|
-
body.envVars = JSON.parse(opts.env);
|
|
4570
|
-
} catch {
|
|
4571
|
-
throw new CLIError("Invalid JSON for --env");
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
const res = await ossFetch("/api/compute/services", {
|
|
4575
|
-
method: "POST",
|
|
4576
|
-
body: JSON.stringify(body)
|
|
4577
|
-
});
|
|
4578
|
-
const service = await res.json();
|
|
4579
|
-
if (json) {
|
|
4580
|
-
outputJson(service);
|
|
4581
|
-
} else {
|
|
4582
|
-
outputSuccess(`Service "${service.name}" created [${service.status}]`);
|
|
4583
|
-
if (service.endpointUrl) {
|
|
4584
|
-
console.log(` Endpoint: ${service.endpointUrl}`);
|
|
4585
|
-
}
|
|
4586
|
-
}
|
|
4587
|
-
await reportCliUsage("cli.compute.create", true);
|
|
4588
|
-
} catch (err) {
|
|
4589
|
-
await reportCliUsage("cli.compute.create", false);
|
|
4590
|
-
handleError(err, json);
|
|
4591
|
-
}
|
|
4592
|
-
});
|
|
4593
|
-
}
|
|
4594
|
-
|
|
4595
4692
|
// src/commands/compute/update.ts
|
|
4596
4693
|
function registerComputeUpdateCommand(computeCmd2) {
|
|
4597
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) => {
|
|
@@ -4747,175 +4844,237 @@ function registerComputeLogsCommand(computeCmd2) {
|
|
|
4747
4844
|
}
|
|
4748
4845
|
|
|
4749
4846
|
// src/commands/compute/deploy.ts
|
|
4750
|
-
import { existsSync as existsSync7
|
|
4751
|
-
import { join as join11 } from "path";
|
|
4752
|
-
import { execSync, spawn } from "child_process";
|
|
4753
|
-
function parseFlyToml(dir) {
|
|
4754
|
-
const tomlPath = join11(dir, "fly.toml");
|
|
4755
|
-
if (!existsSync7(tomlPath)) return {};
|
|
4756
|
-
const content = readFileSync7(tomlPath, "utf-8");
|
|
4757
|
-
const config = {};
|
|
4758
|
-
const portMatch = content.match(/internal_port\s*=\s*(\d+)/);
|
|
4759
|
-
if (portMatch) config.internalPort = Number(portMatch[1]);
|
|
4760
|
-
const regionMatch = content.match(/primary_region\s*=\s*'([^']+)'/);
|
|
4761
|
-
if (regionMatch) config.region = regionMatch[1];
|
|
4762
|
-
const memoryMatch = content.match(/memory\s*=\s*'(\d+)/);
|
|
4763
|
-
if (memoryMatch) config.memory = memoryMatch[1];
|
|
4764
|
-
const cpuKindMatch = content.match(/cpu_kind\s*=\s*'([^']+)'/);
|
|
4765
|
-
if (cpuKindMatch) config.cpuKind = cpuKindMatch[1];
|
|
4766
|
-
const cpusMatch = content.match(/cpus\s*=\s*(\d+)/);
|
|
4767
|
-
if (cpusMatch) config.cpus = Number(cpusMatch[1]);
|
|
4768
|
-
return config;
|
|
4769
|
-
}
|
|
4770
|
-
function memoryToCpuTier(cpuKind, cpus) {
|
|
4771
|
-
if (!cpuKind) return void 0;
|
|
4772
|
-
const kind = cpuKind === "performance" ? "performance" : "shared";
|
|
4773
|
-
const count = cpus ?? 1;
|
|
4774
|
-
return `${kind}-${count}x`;
|
|
4775
|
-
}
|
|
4776
|
-
function generateFlyToml(appName, opts) {
|
|
4777
|
-
const cpuParts = opts.cpu.split("-");
|
|
4778
|
-
const cpuKind = cpuParts[0] ?? "shared";
|
|
4779
|
-
const cpuCount = parseInt(cpuParts[1] ?? "1", 10);
|
|
4780
|
-
return `# Auto-generated by InsForge CLI
|
|
4781
|
-
app = '${appName}'
|
|
4782
|
-
primary_region = '${opts.region}'
|
|
4783
|
-
|
|
4784
|
-
[build]
|
|
4785
|
-
dockerfile = 'Dockerfile'
|
|
4847
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4848
|
+
import { join as join11, resolve as resolve4 } from "path";
|
|
4786
4849
|
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
memory = '${opts.memory}mb'
|
|
4796
|
-
cpu_kind = '${cpuKind}'
|
|
4797
|
-
cpus = ${cpuCount}
|
|
4798
|
-
`;
|
|
4799
|
-
}
|
|
4800
|
-
function checkFlyctl() {
|
|
4801
|
-
try {
|
|
4802
|
-
execSync("flyctl version", { stdio: "pipe" });
|
|
4803
|
-
} catch {
|
|
4850
|
+
// src/lib/flyctl.ts
|
|
4851
|
+
import { spawn, spawnSync } from "child_process";
|
|
4852
|
+
function ensureFlyctlAvailable() {
|
|
4853
|
+
const r = spawnSync("flyctl", ["version"], {
|
|
4854
|
+
encoding: "utf8",
|
|
4855
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4856
|
+
});
|
|
4857
|
+
if (r.error || r.status !== 0) {
|
|
4804
4858
|
throw new CLIError(
|
|
4805
|
-
"flyctl is
|
|
4859
|
+
"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)}` : "")
|
|
4806
4860
|
);
|
|
4807
4861
|
}
|
|
4808
4862
|
}
|
|
4809
|
-
function
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4863
|
+
function flyctlBuildAndPush(opts) {
|
|
4864
|
+
return new Promise((resolve5, reject) => {
|
|
4865
|
+
const child = spawn(
|
|
4866
|
+
"flyctl",
|
|
4867
|
+
[
|
|
4868
|
+
"deploy",
|
|
4869
|
+
"--remote-only",
|
|
4870
|
+
"--build-only",
|
|
4871
|
+
"--push",
|
|
4872
|
+
"--app",
|
|
4873
|
+
opts.appId,
|
|
4874
|
+
"--image-label",
|
|
4875
|
+
opts.imageLabel,
|
|
4876
|
+
"--no-cache"
|
|
4877
|
+
],
|
|
4878
|
+
{
|
|
4879
|
+
cwd: opts.dir,
|
|
4880
|
+
env: { ...process.env, FLY_API_TOKEN: opts.token },
|
|
4881
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
4882
|
+
}
|
|
4814
4883
|
);
|
|
4815
|
-
|
|
4816
|
-
|
|
4884
|
+
let captured = "";
|
|
4885
|
+
child.stdout?.on("data", (b) => {
|
|
4886
|
+
const s = b.toString();
|
|
4887
|
+
captured += s;
|
|
4888
|
+
process.stdout.write(s);
|
|
4889
|
+
});
|
|
4890
|
+
child.stderr?.on("data", (b) => {
|
|
4891
|
+
const s = b.toString();
|
|
4892
|
+
captured += s;
|
|
4893
|
+
process.stderr.write(s);
|
|
4894
|
+
});
|
|
4895
|
+
child.on("error", (err) => {
|
|
4896
|
+
reject(new CLIError(`flyctl deploy could not start: ${err.message}`));
|
|
4897
|
+
});
|
|
4898
|
+
child.on("exit", (code) => {
|
|
4899
|
+
if (code !== 0) {
|
|
4900
|
+
return reject(
|
|
4901
|
+
new CLIError(`flyctl deploy --build-only failed (exit ${code}). See output above.`)
|
|
4902
|
+
);
|
|
4903
|
+
}
|
|
4904
|
+
const m = captured.match(/pushing manifest for registry\.fly\.io\/[^\s]+@(sha256:[0-9a-f]+)/);
|
|
4905
|
+
if (!m) {
|
|
4906
|
+
return reject(
|
|
4907
|
+
new CLIError(
|
|
4908
|
+
'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.'
|
|
4909
|
+
)
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
4912
|
+
resolve5({ imageRef: `registry.fly.io/${opts.appId}@${m[1]}` });
|
|
4913
|
+
});
|
|
4914
|
+
});
|
|
4817
4915
|
}
|
|
4916
|
+
|
|
4917
|
+
// src/commands/compute/deploy.ts
|
|
4818
4918
|
function registerComputeDeployCommand(computeCmd2) {
|
|
4819
|
-
computeCmd2.command("deploy [
|
|
4919
|
+
computeCmd2.command("deploy [dir]").description(
|
|
4920
|
+
"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)"
|
|
4921
|
+
).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(
|
|
4922
|
+
"--cpu <tier>",
|
|
4923
|
+
"CPU tier in <kind>-<N>x format (e.g. shared-1x, performance-2x)",
|
|
4924
|
+
"shared-1x"
|
|
4925
|
+
).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) => {
|
|
4820
4926
|
const { json } = getRootOpts(cmd);
|
|
4821
4927
|
try {
|
|
4822
4928
|
await requireAuth();
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4929
|
+
if (dir && opts.image) {
|
|
4930
|
+
throw new CLIError("Cannot use both [dir] and --image \u2014 pick one mode.");
|
|
4931
|
+
}
|
|
4932
|
+
if (!dir && !opts.image) {
|
|
4933
|
+
throw new CLIError(
|
|
4934
|
+
"Must provide either [dir] (source mode) or --image <url> (image mode)."
|
|
4935
|
+
);
|
|
4936
|
+
}
|
|
4937
|
+
const port = Number(opts.port);
|
|
4938
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
4939
|
+
throw new CLIError(`Invalid --port: ${opts.port}`);
|
|
4940
|
+
}
|
|
4941
|
+
const memory = Number(opts.memory);
|
|
4942
|
+
if (!Number.isInteger(memory) || memory <= 0) {
|
|
4943
|
+
throw new CLIError(`Invalid --memory: ${opts.memory}`);
|
|
4829
4944
|
}
|
|
4830
|
-
const flyTomlDefaults = parseFlyToml(dir);
|
|
4831
|
-
const port = Number(opts.port) || flyTomlDefaults.internalPort || 8080;
|
|
4832
|
-
const cpu = opts.cpu || memoryToCpuTier(flyTomlDefaults.cpuKind, flyTomlDefaults.cpus) || "shared-1x";
|
|
4833
|
-
const memory = Number(opts.memory) || Number(flyTomlDefaults.memory) || 512;
|
|
4834
|
-
const region = opts.region || flyTomlDefaults.region || "iad";
|
|
4835
4945
|
let envVars;
|
|
4836
4946
|
if (opts.env) {
|
|
4947
|
+
let parsed;
|
|
4837
4948
|
try {
|
|
4838
|
-
|
|
4949
|
+
parsed = JSON.parse(opts.env);
|
|
4839
4950
|
} catch {
|
|
4840
4951
|
throw new CLIError("Invalid JSON for --env");
|
|
4841
4952
|
}
|
|
4953
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4954
|
+
throw new CLIError('--env must be a JSON object like {"KEY":"value"}');
|
|
4955
|
+
}
|
|
4956
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
4957
|
+
if (typeof v !== "string") {
|
|
4958
|
+
throw new CLIError(
|
|
4959
|
+
`--env values must be strings \u2014 got ${typeof v} for key "${k}"`
|
|
4960
|
+
);
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
envVars = parsed;
|
|
4842
4964
|
}
|
|
4843
|
-
|
|
4965
|
+
const baseBody = {
|
|
4966
|
+
name: opts.name,
|
|
4967
|
+
port,
|
|
4968
|
+
cpu: opts.cpu,
|
|
4969
|
+
memory,
|
|
4970
|
+
region: opts.region
|
|
4971
|
+
};
|
|
4972
|
+
if (envVars) baseBody.envVars = envVars;
|
|
4973
|
+
if (!dir) {
|
|
4974
|
+
const body = { ...baseBody, imageUrl: opts.image };
|
|
4975
|
+
const listRes2 = await ossFetch("/api/compute/services");
|
|
4976
|
+
const existing2 = (await listRes2.json()).find(
|
|
4977
|
+
(s) => s.name === opts.name
|
|
4978
|
+
);
|
|
4979
|
+
let res;
|
|
4980
|
+
if (existing2) {
|
|
4981
|
+
if (!json) outputInfo(`Found existing service "${opts.name}", updating...`);
|
|
4982
|
+
const updateBody2 = { ...body };
|
|
4983
|
+
delete updateBody2.name;
|
|
4984
|
+
res = await ossFetch(`/api/compute/services/${encodeURIComponent(existing2.id)}`, {
|
|
4985
|
+
method: "PATCH",
|
|
4986
|
+
body: JSON.stringify(updateBody2)
|
|
4987
|
+
});
|
|
4988
|
+
} else {
|
|
4989
|
+
res = await ossFetch("/api/compute/services", {
|
|
4990
|
+
method: "POST",
|
|
4991
|
+
body: JSON.stringify(body)
|
|
4992
|
+
});
|
|
4993
|
+
}
|
|
4994
|
+
const service2 = await res.json();
|
|
4995
|
+
if (json) {
|
|
4996
|
+
outputJson(service2);
|
|
4997
|
+
} else {
|
|
4998
|
+
const verb = existing2 ? "updated" : "deployed";
|
|
4999
|
+
outputSuccess(`Service "${service2.name}" ${verb} [${service2.status}]`);
|
|
5000
|
+
if (service2.endpointUrl) console.log(` Endpoint: ${service2.endpointUrl}`);
|
|
5001
|
+
}
|
|
5002
|
+
await reportCliUsage("cli.compute.deploy", true);
|
|
5003
|
+
return;
|
|
5004
|
+
}
|
|
5005
|
+
const absDir = resolve4(dir);
|
|
5006
|
+
const dockerfilePath = join11(absDir, "Dockerfile");
|
|
5007
|
+
if (!existsSync7(dockerfilePath)) {
|
|
5008
|
+
throw new CLIError(
|
|
5009
|
+
`No Dockerfile at ${dockerfilePath}.
|
|
5010
|
+
Either:
|
|
5011
|
+
\u2022 Create one (ask your AI agent \u2014 see the insforge-cli skill)
|
|
5012
|
+
\u2022 Use --image <url> to deploy a pre-built image instead`
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
ensureFlyctlAvailable();
|
|
5016
|
+
if (!json) outputInfo(`Detected Dockerfile at ${dockerfilePath}`);
|
|
4844
5017
|
const listRes = await ossFetch("/api/compute/services");
|
|
4845
|
-
const
|
|
4846
|
-
const existing = services.find((s) => s.name === opts.name);
|
|
5018
|
+
const existing = (await listRes.json()).find((s) => s.name === opts.name);
|
|
4847
5019
|
let serviceId;
|
|
4848
5020
|
let flyAppId;
|
|
4849
5021
|
if (existing) {
|
|
5022
|
+
if (!existing.flyAppId) {
|
|
5023
|
+
throw new CLIError(
|
|
5024
|
+
`Service "${opts.name}" exists but has no Fly app yet. Delete it and redeploy.`
|
|
5025
|
+
);
|
|
5026
|
+
}
|
|
4850
5027
|
serviceId = existing.id;
|
|
4851
5028
|
flyAppId = existing.flyAppId;
|
|
4852
|
-
if (!json) outputInfo(`Found existing service,
|
|
5029
|
+
if (!json) outputInfo(`Found existing service "${opts.name}" (${flyAppId}), updating...`);
|
|
4853
5030
|
} else {
|
|
4854
5031
|
if (!json) outputInfo(`Creating service "${opts.name}"...`);
|
|
4855
|
-
const
|
|
4856
|
-
name: opts.name,
|
|
4857
|
-
imageUrl: "dockerfile",
|
|
4858
|
-
port,
|
|
4859
|
-
cpu,
|
|
4860
|
-
memory,
|
|
4861
|
-
region
|
|
4862
|
-
};
|
|
4863
|
-
if (envVars) body.envVars = envVars;
|
|
4864
|
-
const deployRes = await ossFetch("/api/compute/services/deploy", {
|
|
5032
|
+
const prepareRes = await ossFetch("/api/compute/services/deploy", {
|
|
4865
5033
|
method: "POST",
|
|
4866
|
-
body: JSON.stringify(
|
|
4867
|
-
});
|
|
4868
|
-
const service = await deployRes.json();
|
|
4869
|
-
serviceId = service.id;
|
|
4870
|
-
flyAppId = service.flyAppId;
|
|
4871
|
-
}
|
|
4872
|
-
const existingTomlPath = join11(dir, "fly.toml");
|
|
4873
|
-
const backupTomlPath = join11(dir, "fly.toml.insforge-backup");
|
|
4874
|
-
let hadExistingToml = false;
|
|
4875
|
-
if (existsSync7(existingTomlPath)) {
|
|
4876
|
-
hadExistingToml = true;
|
|
4877
|
-
renameSync(existingTomlPath, backupTomlPath);
|
|
4878
|
-
}
|
|
4879
|
-
const tomlContent = generateFlyToml(flyAppId, { port, memory, cpu, region });
|
|
4880
|
-
writeFileSync5(existingTomlPath, tomlContent);
|
|
4881
|
-
try {
|
|
4882
|
-
if (!json) outputInfo("Building and deploying (this may take a few minutes)...");
|
|
4883
|
-
await new Promise((resolve4, reject) => {
|
|
4884
|
-
const child = spawn(
|
|
4885
|
-
"flyctl",
|
|
4886
|
-
["deploy", "--remote-only", "--app", flyAppId, "--access-token", flyToken, "--yes"],
|
|
4887
|
-
{ cwd: dir, stdio: json ? "pipe" : "inherit" }
|
|
4888
|
-
);
|
|
4889
|
-
child.on("close", (code) => {
|
|
4890
|
-
if (code === 0) resolve4();
|
|
4891
|
-
else reject(new CLIError(`flyctl deploy failed with exit code ${code}`));
|
|
4892
|
-
});
|
|
4893
|
-
child.on("error", (err) => reject(new CLIError(`flyctl deploy error: ${err.message}`)));
|
|
5034
|
+
body: JSON.stringify(baseBody)
|
|
4894
5035
|
});
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
5036
|
+
const prepared = await prepareRes.json();
|
|
5037
|
+
serviceId = prepared.id;
|
|
5038
|
+
flyAppId = prepared.flyAppId;
|
|
5039
|
+
if (!json) outputInfo(`Created Fly app ${flyAppId}`);
|
|
5040
|
+
}
|
|
5041
|
+
if (!json) outputInfo("Requesting deploy token...");
|
|
5042
|
+
const tokenRes = await ossFetch(
|
|
5043
|
+
`/api/compute/services/${encodeURIComponent(serviceId)}/deploy-token`,
|
|
5044
|
+
{ method: "POST" }
|
|
5045
|
+
);
|
|
5046
|
+
const tokenJson = await tokenRes.json();
|
|
5047
|
+
const imageLabel = `cli-${Date.now()}`;
|
|
5048
|
+
if (!json) outputInfo(`Building & pushing on Fly remote builder...`);
|
|
5049
|
+
const { imageRef } = await flyctlBuildAndPush({
|
|
5050
|
+
dir: absDir,
|
|
5051
|
+
appId: flyAppId,
|
|
5052
|
+
imageLabel,
|
|
5053
|
+
token: tokenJson.token
|
|
5054
|
+
});
|
|
5055
|
+
if (!json) outputInfo("Launching machine...");
|
|
5056
|
+
const updateBody = {
|
|
5057
|
+
imageUrl: imageRef,
|
|
5058
|
+
port,
|
|
5059
|
+
cpu: opts.cpu,
|
|
5060
|
+
memory,
|
|
5061
|
+
region: opts.region
|
|
5062
|
+
};
|
|
5063
|
+
if (envVars) updateBody.envVars = envVars;
|
|
5064
|
+
const finalRes = await ossFetch(
|
|
5065
|
+
`/api/compute/services/${encodeURIComponent(serviceId)}`,
|
|
5066
|
+
{ method: "PATCH", body: JSON.stringify(updateBody) }
|
|
5067
|
+
);
|
|
5068
|
+
const service = await finalRes.json();
|
|
5069
|
+
if (json) {
|
|
5070
|
+
outputJson(service);
|
|
5071
|
+
} else {
|
|
5072
|
+
const verb = existing ? "updated" : "deployed";
|
|
5073
|
+
outputSuccess(`Service "${service.name}" ${verb} [${service.status}]`);
|
|
5074
|
+
if (service.endpointUrl) console.log(` Endpoint: ${service.endpointUrl}`);
|
|
5075
|
+
console.log(` Image: ${imageRef} (built remotely; no local image to clean up)`);
|
|
4918
5076
|
}
|
|
5077
|
+
await reportCliUsage("cli.compute.deploy", true);
|
|
4919
5078
|
} catch (err) {
|
|
4920
5079
|
await reportCliUsage("cli.compute.deploy", false);
|
|
4921
5080
|
handleError(err, json);
|
|
@@ -5602,7 +5761,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
5602
5761
|
const s = !json ? clack10.spinner() : null;
|
|
5603
5762
|
s?.start("Collecting diagnostic data...");
|
|
5604
5763
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
5605
|
-
const cliVersion = "0.1.
|
|
5764
|
+
const cliVersion = "0.1.60";
|
|
5606
5765
|
s?.stop("Data collected");
|
|
5607
5766
|
if (!json) {
|
|
5608
5767
|
console.log(`
|
|
@@ -5831,7 +5990,7 @@ function formatBytesCompact(bytes) {
|
|
|
5831
5990
|
|
|
5832
5991
|
// src/index.ts
|
|
5833
5992
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5834
|
-
var pkg = JSON.parse(
|
|
5993
|
+
var pkg = JSON.parse(readFileSync7(join12(__dirname, "../package.json"), "utf-8"));
|
|
5835
5994
|
var INSFORGE_LOGO = `
|
|
5836
5995
|
\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
|
|
5837
5996
|
\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
|
|
@@ -5903,7 +6062,6 @@ registerDiagnoseCommands(diagnoseCmd);
|
|
|
5903
6062
|
var computeCmd = program.command("compute").description("Manage compute services (Docker containers on Fly.io)");
|
|
5904
6063
|
registerComputeListCommand(computeCmd);
|
|
5905
6064
|
registerComputeGetCommand(computeCmd);
|
|
5906
|
-
registerComputeCreateCommand(computeCmd);
|
|
5907
6065
|
registerComputeDeployCommand(computeCmd);
|
|
5908
6066
|
registerComputeUpdateCommand(computeCmd);
|
|
5909
6067
|
registerComputeDeleteCommand(computeCmd);
|