@insforge/cli 0.1.57 → 0.1.58
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 +122 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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 {
|
|
@@ -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);
|
|
@@ -5602,7 +5717,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
5602
5717
|
const s = !json ? clack10.spinner() : null;
|
|
5603
5718
|
s?.start("Collecting diagnostic data...");
|
|
5604
5719
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
5605
|
-
const cliVersion = "0.1.
|
|
5720
|
+
const cliVersion = "0.1.58";
|
|
5606
5721
|
s?.stop("Data collected");
|
|
5607
5722
|
if (!json) {
|
|
5608
5723
|
console.log(`
|