@instafy/cli 0.1.8-staging.376 → 0.1.8-staging.377
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/api.js +9 -0
- package/dist/errors.js +53 -0
- package/dist/git.js +7 -0
- package/dist/org.js +8 -1
- package/dist/project.js +43 -13
- package/dist/runtime.js +7 -0
- package/dist/tunnel.js +8 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { resolveConfiguredAccessToken } from "./config.js";
|
|
3
|
+
import { formatAuthRejectedError } from "./errors.js";
|
|
3
4
|
function normalizeUrl(raw) {
|
|
4
5
|
const value = (raw ?? "").trim();
|
|
5
6
|
if (!value)
|
|
@@ -123,6 +124,14 @@ export async function requestControllerApi(options) {
|
|
|
123
124
|
const pretty = options.pretty !== false;
|
|
124
125
|
const formattedBody = isJson ? maybePrettyPrintJson(responseText, pretty) : responseText;
|
|
125
126
|
if (!response.ok) {
|
|
127
|
+
if (response.status === 401 || response.status === 403) {
|
|
128
|
+
throw formatAuthRejectedError({
|
|
129
|
+
status: response.status,
|
|
130
|
+
responseBody: responseText,
|
|
131
|
+
retryCommand: "instafy login",
|
|
132
|
+
advancedHint: "pass --access-token / --service-token, or set INSTAFY_ACCESS_TOKEN / INSTAFY_SERVICE_TOKEN",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
126
135
|
const prefix = `Request failed (${response.status} ${response.statusText})`;
|
|
127
136
|
const suffix = formattedBody.trim() ? `: ${formattedBody}` : "";
|
|
128
137
|
throw new Error(`${prefix}${suffix}`);
|
package/dist/errors.js
CHANGED
|
@@ -8,3 +8,56 @@ export function formatAuthRequiredError(params) {
|
|
|
8
8
|
}
|
|
9
9
|
return new Error(lines.join("\n"));
|
|
10
10
|
}
|
|
11
|
+
export function extractControllerErrorMessage(body) {
|
|
12
|
+
const trimmed = body.trim();
|
|
13
|
+
if (!trimmed) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(trimmed);
|
|
18
|
+
if (parsed && typeof parsed === "object") {
|
|
19
|
+
const record = parsed;
|
|
20
|
+
const message = typeof record.message === "string" ? record.message.trim() : "";
|
|
21
|
+
if (message)
|
|
22
|
+
return message;
|
|
23
|
+
const error = typeof record.error === "string" ? record.error.trim() : "";
|
|
24
|
+
if (error)
|
|
25
|
+
return error;
|
|
26
|
+
const hint = typeof record.hint === "string" ? record.hint.trim() : "";
|
|
27
|
+
if (hint)
|
|
28
|
+
return hint;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// ignore malformed json
|
|
33
|
+
}
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
export function formatAuthRejectedError(params) {
|
|
37
|
+
const status = params?.status ?? 0;
|
|
38
|
+
const message = params?.responseBody ? extractControllerErrorMessage(params.responseBody) : null;
|
|
39
|
+
const normalized = (message ?? "").toLowerCase();
|
|
40
|
+
const headline = (() => {
|
|
41
|
+
if (status === 403) {
|
|
42
|
+
return "Access denied. Make sure you are signed into the right account (`instafy login`).";
|
|
43
|
+
}
|
|
44
|
+
if (status === 401 && normalized.includes("expired")) {
|
|
45
|
+
return "Your login has expired. Run `instafy login` again.";
|
|
46
|
+
}
|
|
47
|
+
if (status === 401) {
|
|
48
|
+
return "Not authenticated. Run `instafy login` again.";
|
|
49
|
+
}
|
|
50
|
+
return "Not authenticated. Run `instafy login` first.";
|
|
51
|
+
})();
|
|
52
|
+
const lines = [headline];
|
|
53
|
+
if (params?.retryCommand) {
|
|
54
|
+
lines.push("", `Then retry: ${params.retryCommand}`);
|
|
55
|
+
}
|
|
56
|
+
if (message) {
|
|
57
|
+
lines.push("", `Server: ${message}`);
|
|
58
|
+
}
|
|
59
|
+
if (params?.advancedHint) {
|
|
60
|
+
lines.push("", `Advanced: ${params.advancedHint}`);
|
|
61
|
+
}
|
|
62
|
+
return new Error(lines.join("\n"));
|
|
63
|
+
}
|
package/dist/git.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
4
|
import { resolveConfiguredAccessToken, resolveControllerUrl } from "./config.js";
|
|
5
|
+
import { formatAuthRejectedError } from "./errors.js";
|
|
5
6
|
function normalizeToken(value) {
|
|
6
7
|
if (typeof value !== "string") {
|
|
7
8
|
return null;
|
|
@@ -52,6 +53,12 @@ export async function mintGitAccessToken(params) {
|
|
|
52
53
|
});
|
|
53
54
|
if (!response.ok) {
|
|
54
55
|
const text = await response.text().catch(() => "");
|
|
56
|
+
if (response.status === 401 || response.status === 403) {
|
|
57
|
+
throw formatAuthRejectedError({
|
|
58
|
+
status: response.status,
|
|
59
|
+
responseBody: text,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
55
62
|
throw new Error(`Instafy server rejected git token request (${response.status} ${response.statusText}): ${text}`);
|
|
56
63
|
}
|
|
57
64
|
const payload = (await response.json());
|
package/dist/org.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import kleur from "kleur";
|
|
2
2
|
import { resolveControllerUrl, resolveUserAccessToken } from "./config.js";
|
|
3
|
-
import { formatAuthRequiredError } from "./errors.js";
|
|
3
|
+
import { formatAuthRejectedError, formatAuthRequiredError } from "./errors.js";
|
|
4
4
|
export async function listOrganizations(params) {
|
|
5
5
|
const controllerUrl = resolveControllerUrl({ controllerUrl: params.controllerUrl ?? null });
|
|
6
6
|
const token = resolveUserAccessToken({ accessToken: params.accessToken ?? null });
|
|
@@ -12,6 +12,13 @@ export async function listOrganizations(params) {
|
|
|
12
12
|
});
|
|
13
13
|
if (!response.ok) {
|
|
14
14
|
const text = await response.text().catch(() => "");
|
|
15
|
+
if (response.status === 401 || response.status === 403) {
|
|
16
|
+
throw formatAuthRejectedError({
|
|
17
|
+
status: response.status,
|
|
18
|
+
responseBody: text,
|
|
19
|
+
retryCommand: "instafy org list",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
15
22
|
throw new Error(`Organization list failed (${response.status} ${response.statusText}): ${text}`);
|
|
16
23
|
}
|
|
17
24
|
const body = (await response.json());
|
package/dist/project.js
CHANGED
|
@@ -3,14 +3,14 @@ import path from "node:path";
|
|
|
3
3
|
import kleur from "kleur";
|
|
4
4
|
import { stdin as input } from "node:process";
|
|
5
5
|
import { getInstafyProfileConfigPath, resolveConfiguredStudioUrl, resolveControllerUrl, resolveUserAccessToken, } from "./config.js";
|
|
6
|
-
import { formatAuthRequiredError } from "./errors.js";
|
|
6
|
+
import { formatAuthRejectedError, formatAuthRequiredError } from "./errors.js";
|
|
7
7
|
import { findProjectManifest } from "./project-manifest.js";
|
|
8
8
|
let promptsModule = null;
|
|
9
9
|
async function loadPrompts() {
|
|
10
10
|
promptsModule ?? (promptsModule = import("@clack/prompts"));
|
|
11
11
|
return promptsModule;
|
|
12
12
|
}
|
|
13
|
-
async function fetchOrganizations(controllerUrl, token) {
|
|
13
|
+
async function fetchOrganizations(controllerUrl, token, retryCommand) {
|
|
14
14
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
15
15
|
headers: {
|
|
16
16
|
authorization: `Bearer ${token}`,
|
|
@@ -18,12 +18,19 @@ async function fetchOrganizations(controllerUrl, token) {
|
|
|
18
18
|
});
|
|
19
19
|
if (!response.ok) {
|
|
20
20
|
const text = await response.text().catch(() => "");
|
|
21
|
+
if (response.status === 401 || response.status === 403) {
|
|
22
|
+
throw formatAuthRejectedError({
|
|
23
|
+
status: response.status,
|
|
24
|
+
responseBody: text,
|
|
25
|
+
retryCommand,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
21
28
|
throw new Error(`Organization list failed (${response.status} ${response.statusText}): ${text}`);
|
|
22
29
|
}
|
|
23
30
|
const body = (await response.json());
|
|
24
31
|
return Array.isArray(body.orgs) ? body.orgs : [];
|
|
25
32
|
}
|
|
26
|
-
async function fetchOrgProjects(controllerUrl, token, orgId) {
|
|
33
|
+
async function fetchOrgProjects(controllerUrl, token, orgId, retryCommand) {
|
|
27
34
|
const response = await fetch(`${controllerUrl}/orgs/${encodeURIComponent(orgId)}/projects`, {
|
|
28
35
|
headers: {
|
|
29
36
|
authorization: `Bearer ${token}`,
|
|
@@ -31,12 +38,19 @@ async function fetchOrgProjects(controllerUrl, token, orgId) {
|
|
|
31
38
|
});
|
|
32
39
|
if (!response.ok) {
|
|
33
40
|
const text = await response.text().catch(() => "");
|
|
41
|
+
if (response.status === 401 || response.status === 403) {
|
|
42
|
+
throw formatAuthRejectedError({
|
|
43
|
+
status: response.status,
|
|
44
|
+
responseBody: text,
|
|
45
|
+
retryCommand,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
34
48
|
throw new Error(`Project list failed (${response.status} ${response.statusText}): ${text}`);
|
|
35
49
|
}
|
|
36
50
|
const body = (await response.json());
|
|
37
51
|
return Array.isArray(body.projects) ? body.projects : [];
|
|
38
52
|
}
|
|
39
|
-
async function createOrganization(controllerUrl, token, payload) {
|
|
53
|
+
async function createOrganization(controllerUrl, token, payload, retryCommand) {
|
|
40
54
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
41
55
|
method: "POST",
|
|
42
56
|
headers: {
|
|
@@ -47,6 +61,13 @@ async function createOrganization(controllerUrl, token, payload) {
|
|
|
47
61
|
});
|
|
48
62
|
if (!response.ok) {
|
|
49
63
|
const text = await response.text().catch(() => "");
|
|
64
|
+
if (response.status === 401 || response.status === 403) {
|
|
65
|
+
throw formatAuthRejectedError({
|
|
66
|
+
status: response.status,
|
|
67
|
+
responseBody: text,
|
|
68
|
+
retryCommand,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
50
71
|
throw new Error(`Organization creation failed (${response.status} ${response.statusText}): ${text}`);
|
|
51
72
|
}
|
|
52
73
|
const json = (await response.json());
|
|
@@ -56,7 +77,7 @@ async function createOrganization(controllerUrl, token, payload) {
|
|
|
56
77
|
}
|
|
57
78
|
return { orgId, orgName: json.org_name ?? json.orgName ?? null };
|
|
58
79
|
}
|
|
59
|
-
async function resolveOrg(controllerUrl, token, options) {
|
|
80
|
+
async function resolveOrg(controllerUrl, token, options, retryCommand) {
|
|
60
81
|
if (options.orgId) {
|
|
61
82
|
return { orgId: options.orgId, orgName: options.orgName ?? null };
|
|
62
83
|
}
|
|
@@ -91,18 +112,18 @@ async function resolveOrg(controllerUrl, token, options) {
|
|
|
91
112
|
if (chosenSlug) {
|
|
92
113
|
payload.orgSlug = chosenSlug;
|
|
93
114
|
}
|
|
94
|
-
const created = await createOrganization(controllerUrl, token, payload);
|
|
115
|
+
const created = await createOrganization(controllerUrl, token, payload, retryCommand);
|
|
95
116
|
return { orgId: created.orgId, orgName: created.orgName ?? chosenName };
|
|
96
117
|
}
|
|
97
118
|
if (orgSlug) {
|
|
98
|
-
const orgs = await fetchOrganizations(controllerUrl, token);
|
|
119
|
+
const orgs = await fetchOrganizations(controllerUrl, token, retryCommand);
|
|
99
120
|
const matches = orgs.filter((org) => org.slug === orgSlug);
|
|
100
121
|
if (matches.length === 1) {
|
|
101
122
|
return { orgId: matches[0].id, orgName: matches[0].name ?? null };
|
|
102
123
|
}
|
|
103
124
|
}
|
|
104
125
|
if (!orgSlug && !orgName) {
|
|
105
|
-
const orgs = await fetchOrganizations(controllerUrl, token);
|
|
126
|
+
const orgs = await fetchOrganizations(controllerUrl, token, retryCommand);
|
|
106
127
|
if (orgs.length === 0) {
|
|
107
128
|
if (allowInteractive) {
|
|
108
129
|
const { confirm, isCancel } = await loadPrompts();
|
|
@@ -170,7 +191,7 @@ async function resolveOrg(controllerUrl, token, options) {
|
|
|
170
191
|
if (options.ownerUserId) {
|
|
171
192
|
payload.ownerUserId = options.ownerUserId;
|
|
172
193
|
}
|
|
173
|
-
const created = await createOrganization(controllerUrl, token, payload);
|
|
194
|
+
const created = await createOrganization(controllerUrl, token, payload, retryCommand);
|
|
174
195
|
return { orgId: created.orgId, orgName: created.orgName ?? chosenName };
|
|
175
196
|
}
|
|
176
197
|
throw new Error(`Organization slug "${orgSlug}" did not match an existing org, and org name is required to create one.\n\nNext:\n- Create an org in Studio: ${studioOrgUrl}\n- Or rerun: instafy project init --org-name \"My Org\" --org-slug "${orgSlug}"`);
|
|
@@ -188,7 +209,7 @@ async function resolveOrg(controllerUrl, token, options) {
|
|
|
188
209
|
if (!payload.orgName) {
|
|
189
210
|
throw new Error(`Organization name is required.\n\nNext:\n- Create an org in Studio: ${studioOrgUrl}\n- Or rerun: instafy project init --org-name \"My Org\"`);
|
|
190
211
|
}
|
|
191
|
-
const created = await createOrganization(controllerUrl, token, payload);
|
|
212
|
+
const created = await createOrganization(controllerUrl, token, payload, retryCommand);
|
|
192
213
|
return { orgId: created.orgId, orgName: created.orgName ?? orgName ?? null };
|
|
193
214
|
}
|
|
194
215
|
export async function listProjects(options) {
|
|
@@ -197,7 +218,8 @@ export async function listProjects(options) {
|
|
|
197
218
|
if (!token) {
|
|
198
219
|
throw formatAuthRequiredError({ retryCommand: "instafy project list" });
|
|
199
220
|
}
|
|
200
|
-
const
|
|
221
|
+
const retryCommand = "instafy project list";
|
|
222
|
+
const orgs = await fetchOrganizations(controllerUrl, token, retryCommand);
|
|
201
223
|
let targetOrgs = orgs;
|
|
202
224
|
if (options.orgId) {
|
|
203
225
|
targetOrgs = orgs.filter((org) => org.id === options.orgId);
|
|
@@ -213,7 +235,7 @@ export async function listProjects(options) {
|
|
|
213
235
|
}
|
|
214
236
|
const summaries = [];
|
|
215
237
|
for (const org of targetOrgs) {
|
|
216
|
-
const projects = await fetchOrgProjects(controllerUrl, token, org.id);
|
|
238
|
+
const projects = await fetchOrgProjects(controllerUrl, token, org.id, retryCommand);
|
|
217
239
|
summaries.push({ org, projects });
|
|
218
240
|
}
|
|
219
241
|
if (options.json) {
|
|
@@ -256,7 +278,8 @@ export async function projectInit(options) {
|
|
|
256
278
|
advancedHint: "pass --access-token or set INSTAFY_ACCESS_TOKEN / SUPABASE_ACCESS_TOKEN",
|
|
257
279
|
});
|
|
258
280
|
}
|
|
259
|
-
const
|
|
281
|
+
const retryCommand = "instafy project init";
|
|
282
|
+
const org = await resolveOrg(controllerUrl, token, options, retryCommand);
|
|
260
283
|
const body = {
|
|
261
284
|
projectType: options.projectType,
|
|
262
285
|
ownerUserId: options.ownerUserId,
|
|
@@ -271,6 +294,13 @@ export async function projectInit(options) {
|
|
|
271
294
|
});
|
|
272
295
|
if (!response.ok) {
|
|
273
296
|
const text = await response.text().catch(() => "");
|
|
297
|
+
if (response.status === 401 || response.status === 403) {
|
|
298
|
+
throw formatAuthRejectedError({
|
|
299
|
+
status: response.status,
|
|
300
|
+
responseBody: text,
|
|
301
|
+
retryCommand,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
274
304
|
throw new Error(`Project creation failed (${response.status} ${response.statusText}): ${text}`);
|
|
275
305
|
}
|
|
276
306
|
const json = (await response.json());
|
package/dist/runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import { ensureRatholeBinary } from "./rathole.js";
|
|
9
9
|
import { resolveConfiguredAccessToken } from "./config.js";
|
|
10
|
+
import { formatAuthRejectedError } from "./errors.js";
|
|
10
11
|
import { findProjectManifest } from "./project-manifest.js";
|
|
11
12
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
12
13
|
const STATE_FILE = path.join(INSTAFY_DIR, "cli-runtime-state.json");
|
|
@@ -257,6 +258,12 @@ export async function mintRuntimeAccessToken(params) {
|
|
|
257
258
|
});
|
|
258
259
|
if (!response.ok) {
|
|
259
260
|
const text = await response.text().catch(() => "");
|
|
261
|
+
if (response.status === 401 || response.status === 403) {
|
|
262
|
+
throw formatAuthRejectedError({
|
|
263
|
+
status: response.status,
|
|
264
|
+
responseBody: text,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
260
267
|
throw new Error(`Instafy server rejected runtime token request (${response.status} ${response.statusText}): ${text}`);
|
|
261
268
|
}
|
|
262
269
|
const payload = (await response.json());
|
package/dist/tunnel.js
CHANGED
|
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import kleur from "kleur";
|
|
7
7
|
import { findProjectManifest, resolveRatholeBinaryForCli } from "./runtime.js";
|
|
8
8
|
import { resolveConfiguredControllerUrl, resolveControllerUrl as resolveDefaultControllerUrl, resolveUserAccessToken, } from "./config.js";
|
|
9
|
-
import { formatAuthRequiredError } from "./errors.js";
|
|
9
|
+
import { formatAuthRejectedError, formatAuthRequiredError } from "./errors.js";
|
|
10
10
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
11
11
|
const TUNNEL_STATE_FILE = path.join(INSTAFY_DIR, "cli-tunnel-state.json");
|
|
12
12
|
const TUNNEL_LOG_DIR = path.join(INSTAFY_DIR, "cli-tunnel-logs");
|
|
@@ -169,6 +169,13 @@ async function requestTunnel(controllerUrl, token, projectId, metadata) {
|
|
|
169
169
|
});
|
|
170
170
|
if (!response.ok) {
|
|
171
171
|
const text = await response.text().catch(() => "");
|
|
172
|
+
if (response.status === 401 || response.status === 403) {
|
|
173
|
+
throw formatAuthRejectedError({
|
|
174
|
+
status: response.status,
|
|
175
|
+
responseBody: text,
|
|
176
|
+
retryCommand: "instafy tunnel start",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
172
179
|
throw new Error(`Tunnel request failed (${response.status} ${response.statusText}): ${text}`);
|
|
173
180
|
}
|
|
174
181
|
const body = (await response.json());
|
package/package.json
CHANGED