@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 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 orgs = await fetchOrganizations(controllerUrl, token);
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 org = await resolveOrg(controllerUrl, token, options);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instafy/cli",
3
- "version": "0.1.8-staging.376",
3
+ "version": "0.1.8-staging.377",
4
4
  "description": "Run Instafy projects locally, link folders to Studio, and share previews/webhooks via tunnels.",
5
5
  "private": false,
6
6
  "publishConfig": {