@instafy/cli 0.1.8 → 0.1.9
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/README.md +16 -9
- package/dist/api.js +9 -0
- package/dist/auth.js +392 -23
- package/dist/config.js +150 -6
- package/dist/errors.js +63 -0
- package/dist/git-credential.js +205 -0
- package/dist/git-setup.js +56 -0
- package/dist/git-wrapper.js +502 -0
- package/dist/git.js +11 -5
- package/dist/index.js +293 -108
- package/dist/org.js +9 -4
- package/dist/project-manifest.js +24 -0
- package/dist/project.js +254 -29
- package/dist/rathole.js +14 -10
- package/dist/runtime.js +11 -24
- package/dist/tunnel.js +272 -7
- package/package.json +3 -1
package/dist/project.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { stdin as input } from "node:process";
|
|
5
|
+
import { getInstafyProfileConfigPath, resolveConfiguredStudioUrl, resolveControllerUrl, resolveUserAccessToken, } from "./config.js";
|
|
6
|
+
import { formatAuthRejectedError, formatAuthRequiredError } from "./errors.js";
|
|
7
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
8
|
+
let promptsModule = null;
|
|
9
|
+
async function loadPrompts() {
|
|
10
|
+
promptsModule ?? (promptsModule = import("@clack/prompts"));
|
|
11
|
+
return promptsModule;
|
|
7
12
|
}
|
|
8
|
-
async function fetchOrganizations(controllerUrl, token) {
|
|
13
|
+
async function fetchOrganizations(controllerUrl, token, retryCommand) {
|
|
9
14
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
10
15
|
headers: {
|
|
11
16
|
authorization: `Bearer ${token}`,
|
|
@@ -13,12 +18,19 @@ async function fetchOrganizations(controllerUrl, token) {
|
|
|
13
18
|
});
|
|
14
19
|
if (!response.ok) {
|
|
15
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
|
+
}
|
|
16
28
|
throw new Error(`Organization list failed (${response.status} ${response.statusText}): ${text}`);
|
|
17
29
|
}
|
|
18
30
|
const body = (await response.json());
|
|
19
31
|
return Array.isArray(body.orgs) ? body.orgs : [];
|
|
20
32
|
}
|
|
21
|
-
async function fetchOrgProjects(controllerUrl, token, orgId) {
|
|
33
|
+
async function fetchOrgProjects(controllerUrl, token, orgId, retryCommand) {
|
|
22
34
|
const response = await fetch(`${controllerUrl}/orgs/${encodeURIComponent(orgId)}/projects`, {
|
|
23
35
|
headers: {
|
|
24
36
|
authorization: `Bearer ${token}`,
|
|
@@ -26,25 +38,19 @@ async function fetchOrgProjects(controllerUrl, token, orgId) {
|
|
|
26
38
|
});
|
|
27
39
|
if (!response.ok) {
|
|
28
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
|
+
}
|
|
29
48
|
throw new Error(`Project list failed (${response.status} ${response.statusText}): ${text}`);
|
|
30
49
|
}
|
|
31
50
|
const body = (await response.json());
|
|
32
51
|
return Array.isArray(body.projects) ? body.projects : [];
|
|
33
52
|
}
|
|
34
|
-
async function
|
|
35
|
-
if (options.orgId) {
|
|
36
|
-
return { orgId: options.orgId, orgName: options.orgName ?? null };
|
|
37
|
-
}
|
|
38
|
-
const payload = {};
|
|
39
|
-
if (options.orgName) {
|
|
40
|
-
payload.orgName = options.orgName;
|
|
41
|
-
}
|
|
42
|
-
if (options.orgSlug) {
|
|
43
|
-
payload.orgSlug = options.orgSlug;
|
|
44
|
-
}
|
|
45
|
-
if (options.ownerUserId) {
|
|
46
|
-
payload.ownerUserId = options.ownerUserId;
|
|
47
|
-
}
|
|
53
|
+
async function createOrganization(controllerUrl, token, payload, retryCommand) {
|
|
48
54
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
49
55
|
method: "POST",
|
|
50
56
|
headers: {
|
|
@@ -55,22 +61,165 @@ async function resolveOrg(controllerUrl, token, options) {
|
|
|
55
61
|
});
|
|
56
62
|
if (!response.ok) {
|
|
57
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
|
+
}
|
|
58
71
|
throw new Error(`Organization creation failed (${response.status} ${response.statusText}): ${text}`);
|
|
59
72
|
}
|
|
60
73
|
const json = (await response.json());
|
|
61
|
-
const orgId = json.org_id;
|
|
74
|
+
const orgId = json.org_id ?? json.orgId ?? json.id;
|
|
62
75
|
if (!orgId) {
|
|
63
|
-
throw new Error(
|
|
76
|
+
throw new Error(`Organization creation response missing org id (expected org_id/orgId/id). Raw: ${JSON.stringify(json)}`);
|
|
64
77
|
}
|
|
65
|
-
return { orgId, orgName: json.org_name ?? null };
|
|
78
|
+
return { orgId, orgName: json.org_name ?? json.orgName ?? null };
|
|
79
|
+
}
|
|
80
|
+
async function resolveOrg(controllerUrl, token, options, retryCommand) {
|
|
81
|
+
if (options.orgId) {
|
|
82
|
+
return { orgId: options.orgId, orgName: options.orgName ?? null };
|
|
83
|
+
}
|
|
84
|
+
const orgSlug = options.orgSlug?.trim() || null;
|
|
85
|
+
const orgName = options.orgName?.trim() || null;
|
|
86
|
+
const studioUrl = resolveConfiguredStudioUrl({ profile: options.profile ?? null }) ?? "https://staging.instafy.dev";
|
|
87
|
+
const studioOrgUrl = `${studioUrl.replace(/\/$/, "")}/studio?panel=settings`;
|
|
88
|
+
const allowInteractive = Boolean(input.isTTY && process.stdout.isTTY && options.json !== true && process.env.CI !== "true");
|
|
89
|
+
async function promptAndCreateOrg() {
|
|
90
|
+
const { isCancel, text } = await loadPrompts();
|
|
91
|
+
const enteredName = await text({
|
|
92
|
+
message: "Organization name",
|
|
93
|
+
defaultValue: "Personal",
|
|
94
|
+
});
|
|
95
|
+
if (isCancel(enteredName)) {
|
|
96
|
+
throw new Error("Cancelled.");
|
|
97
|
+
}
|
|
98
|
+
const chosenName = String(enteredName).trim() || "Personal";
|
|
99
|
+
const enteredSlug = await text({
|
|
100
|
+
message: "Organization slug (optional)",
|
|
101
|
+
});
|
|
102
|
+
if (isCancel(enteredSlug)) {
|
|
103
|
+
throw new Error("Cancelled.");
|
|
104
|
+
}
|
|
105
|
+
const chosenSlug = String(enteredSlug).trim();
|
|
106
|
+
const payload = {
|
|
107
|
+
orgName: chosenName,
|
|
108
|
+
};
|
|
109
|
+
if (options.ownerUserId) {
|
|
110
|
+
payload.ownerUserId = options.ownerUserId;
|
|
111
|
+
}
|
|
112
|
+
if (chosenSlug) {
|
|
113
|
+
payload.orgSlug = chosenSlug;
|
|
114
|
+
}
|
|
115
|
+
const created = await createOrganization(controllerUrl, token, payload, retryCommand);
|
|
116
|
+
return { orgId: created.orgId, orgName: created.orgName ?? chosenName };
|
|
117
|
+
}
|
|
118
|
+
if (orgSlug) {
|
|
119
|
+
const orgs = await fetchOrganizations(controllerUrl, token, retryCommand);
|
|
120
|
+
const matches = orgs.filter((org) => org.slug === orgSlug);
|
|
121
|
+
if (matches.length === 1) {
|
|
122
|
+
return { orgId: matches[0].id, orgName: matches[0].name ?? null };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!orgSlug && !orgName) {
|
|
126
|
+
const orgs = await fetchOrganizations(controllerUrl, token, retryCommand);
|
|
127
|
+
if (orgs.length === 0) {
|
|
128
|
+
if (allowInteractive) {
|
|
129
|
+
const { confirm, isCancel } = await loadPrompts();
|
|
130
|
+
console.log(kleur.yellow("No organizations found for this account."));
|
|
131
|
+
console.log("");
|
|
132
|
+
console.log(`Create one in Studio: ${kleur.cyan(studioOrgUrl)}`);
|
|
133
|
+
console.log("");
|
|
134
|
+
const shouldCreate = await confirm({
|
|
135
|
+
message: "Create a new organization now?",
|
|
136
|
+
initialValue: true,
|
|
137
|
+
});
|
|
138
|
+
if (isCancel(shouldCreate) || !shouldCreate) {
|
|
139
|
+
throw new Error("No organization selected.");
|
|
140
|
+
}
|
|
141
|
+
return await promptAndCreateOrg();
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`No organizations found.\n\nNext:\n- Create an org in Studio: ${studioOrgUrl}\n- Or rerun: instafy project init --org-name \"My Org\"`);
|
|
144
|
+
}
|
|
145
|
+
if (orgs.length === 1) {
|
|
146
|
+
return { orgId: orgs[0].id, orgName: orgs[0].name ?? null };
|
|
147
|
+
}
|
|
148
|
+
if (allowInteractive) {
|
|
149
|
+
const { isCancel, select } = await loadPrompts();
|
|
150
|
+
const selection = await select({
|
|
151
|
+
message: "Choose an organization for this project",
|
|
152
|
+
options: [
|
|
153
|
+
{ value: "__create__", label: "+ Create a new organization" },
|
|
154
|
+
...orgs.map((org) => ({
|
|
155
|
+
value: org.id,
|
|
156
|
+
label: org.name,
|
|
157
|
+
hint: `${org.slug ? `${org.slug} · ` : ""}${org.id}`,
|
|
158
|
+
})),
|
|
159
|
+
],
|
|
160
|
+
initialValue: orgs[0].id,
|
|
161
|
+
});
|
|
162
|
+
if (isCancel(selection)) {
|
|
163
|
+
throw new Error("Cancelled.");
|
|
164
|
+
}
|
|
165
|
+
if (selection === "__create__") {
|
|
166
|
+
return await promptAndCreateOrg();
|
|
167
|
+
}
|
|
168
|
+
const pickedOrg = orgs.find((org) => org.id === selection);
|
|
169
|
+
if (!pickedOrg) {
|
|
170
|
+
throw new Error("Selected organization not found.");
|
|
171
|
+
}
|
|
172
|
+
return { orgId: pickedOrg.id, orgName: pickedOrg.name ?? null };
|
|
173
|
+
}
|
|
174
|
+
throw new Error("Multiple organizations found.\n\nNext:\n- instafy org list\n- instafy project init --org-id <uuid>");
|
|
175
|
+
}
|
|
176
|
+
if (orgSlug && !orgName) {
|
|
177
|
+
if (allowInteractive) {
|
|
178
|
+
const { isCancel, text } = await loadPrompts();
|
|
179
|
+
console.log(kleur.yellow("Organization slug did not match an existing org."));
|
|
180
|
+
console.log(`Create one in Studio: ${kleur.cyan(studioOrgUrl)}`);
|
|
181
|
+
console.log("");
|
|
182
|
+
const enteredName = await text({
|
|
183
|
+
message: "Organization name",
|
|
184
|
+
defaultValue: "Personal",
|
|
185
|
+
});
|
|
186
|
+
if (isCancel(enteredName)) {
|
|
187
|
+
throw new Error("Cancelled.");
|
|
188
|
+
}
|
|
189
|
+
const chosenName = String(enteredName).trim() || "Personal";
|
|
190
|
+
const payload = { orgName: chosenName, orgSlug };
|
|
191
|
+
if (options.ownerUserId) {
|
|
192
|
+
payload.ownerUserId = options.ownerUserId;
|
|
193
|
+
}
|
|
194
|
+
const created = await createOrganization(controllerUrl, token, payload, retryCommand);
|
|
195
|
+
return { orgId: created.orgId, orgName: created.orgName ?? chosenName };
|
|
196
|
+
}
|
|
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}"`);
|
|
198
|
+
}
|
|
199
|
+
const payload = {};
|
|
200
|
+
if (orgName) {
|
|
201
|
+
payload.orgName = orgName;
|
|
202
|
+
}
|
|
203
|
+
if (orgSlug) {
|
|
204
|
+
payload.orgSlug = orgSlug;
|
|
205
|
+
}
|
|
206
|
+
if (options.ownerUserId) {
|
|
207
|
+
payload.ownerUserId = options.ownerUserId;
|
|
208
|
+
}
|
|
209
|
+
if (!payload.orgName) {
|
|
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\"`);
|
|
211
|
+
}
|
|
212
|
+
const created = await createOrganization(controllerUrl, token, payload, retryCommand);
|
|
213
|
+
return { orgId: created.orgId, orgName: created.orgName ?? orgName ?? null };
|
|
66
214
|
}
|
|
67
215
|
export async function listProjects(options) {
|
|
68
216
|
const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
|
|
69
217
|
const token = resolveUserAccessToken({ accessToken: options.accessToken ?? null });
|
|
70
218
|
if (!token) {
|
|
71
|
-
throw formatAuthRequiredError();
|
|
219
|
+
throw formatAuthRequiredError({ retryCommand: "instafy project list" });
|
|
72
220
|
}
|
|
73
|
-
const
|
|
221
|
+
const retryCommand = "instafy project list";
|
|
222
|
+
const orgs = await fetchOrganizations(controllerUrl, token, retryCommand);
|
|
74
223
|
let targetOrgs = orgs;
|
|
75
224
|
if (options.orgId) {
|
|
76
225
|
targetOrgs = orgs.filter((org) => org.id === options.orgId);
|
|
@@ -86,7 +235,7 @@ export async function listProjects(options) {
|
|
|
86
235
|
}
|
|
87
236
|
const summaries = [];
|
|
88
237
|
for (const org of targetOrgs) {
|
|
89
|
-
const projects = await fetchOrgProjects(controllerUrl, token, org.id);
|
|
238
|
+
const projects = await fetchOrgProjects(controllerUrl, token, org.id, retryCommand);
|
|
90
239
|
summaries.push({ org, projects });
|
|
91
240
|
}
|
|
92
241
|
if (options.json) {
|
|
@@ -113,12 +262,24 @@ export async function listProjects(options) {
|
|
|
113
262
|
}
|
|
114
263
|
export async function projectInit(options) {
|
|
115
264
|
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
116
|
-
const controllerUrl = resolveControllerUrl({
|
|
117
|
-
|
|
265
|
+
const controllerUrl = resolveControllerUrl({
|
|
266
|
+
controllerUrl: options.controllerUrl ?? null,
|
|
267
|
+
profile: options.profile ?? null,
|
|
268
|
+
cwd: rootDir,
|
|
269
|
+
});
|
|
270
|
+
const token = resolveUserAccessToken({
|
|
271
|
+
accessToken: options.accessToken ?? null,
|
|
272
|
+
profile: options.profile ?? null,
|
|
273
|
+
cwd: rootDir,
|
|
274
|
+
});
|
|
118
275
|
if (!token) {
|
|
119
|
-
throw formatAuthRequiredError(
|
|
276
|
+
throw formatAuthRequiredError({
|
|
277
|
+
retryCommand: "instafy project init",
|
|
278
|
+
advancedHint: "pass --access-token or set INSTAFY_ACCESS_TOKEN / SUPABASE_ACCESS_TOKEN",
|
|
279
|
+
});
|
|
120
280
|
}
|
|
121
|
-
const
|
|
281
|
+
const retryCommand = "instafy project init";
|
|
282
|
+
const org = await resolveOrg(controllerUrl, token, options, retryCommand);
|
|
122
283
|
const body = {
|
|
123
284
|
projectType: options.projectType,
|
|
124
285
|
ownerUserId: options.ownerUserId,
|
|
@@ -133,9 +294,28 @@ export async function projectInit(options) {
|
|
|
133
294
|
});
|
|
134
295
|
if (!response.ok) {
|
|
135
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
|
+
}
|
|
136
304
|
throw new Error(`Project creation failed (${response.status} ${response.statusText}): ${text}`);
|
|
137
305
|
}
|
|
138
306
|
const json = (await response.json());
|
|
307
|
+
if (!json.project_id && json.projectId) {
|
|
308
|
+
json.project_id = json.projectId;
|
|
309
|
+
}
|
|
310
|
+
if (!json.org_id && json.orgId) {
|
|
311
|
+
json.org_id = json.orgId;
|
|
312
|
+
}
|
|
313
|
+
if (!json.org_name && json.orgName) {
|
|
314
|
+
json.org_name = json.orgName;
|
|
315
|
+
}
|
|
316
|
+
if (!json.project_id) {
|
|
317
|
+
throw new Error(`Project creation response missing project id (expected project_id/projectId). Raw: ${JSON.stringify(json)}`);
|
|
318
|
+
}
|
|
139
319
|
const manifestDir = path.join(rootDir, ".instafy");
|
|
140
320
|
const manifestPath = path.join(manifestDir, "project.json");
|
|
141
321
|
try {
|
|
@@ -145,6 +325,7 @@ export async function projectInit(options) {
|
|
|
145
325
|
orgId: json.org_id ?? org.orgId ?? null,
|
|
146
326
|
orgName: json.org_name ?? org.orgName ?? null,
|
|
147
327
|
controllerUrl,
|
|
328
|
+
profile: options.profile ?? null,
|
|
148
329
|
createdAt: new Date().toISOString(),
|
|
149
330
|
}, null, 2), "utf8");
|
|
150
331
|
}
|
|
@@ -170,3 +351,47 @@ export async function projectInit(options) {
|
|
|
170
351
|
}
|
|
171
352
|
return json;
|
|
172
353
|
}
|
|
354
|
+
export function projectProfile(options) {
|
|
355
|
+
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
356
|
+
const manifestInfo = findProjectManifest(rootDir);
|
|
357
|
+
if (!manifestInfo.path || !manifestInfo.manifest) {
|
|
358
|
+
throw new Error("No project configured. Run `instafy project init` in this folder first.");
|
|
359
|
+
}
|
|
360
|
+
const shouldUpdate = options.unset === true || typeof options.profile === "string";
|
|
361
|
+
const currentProfile = typeof manifestInfo.manifest.profile === "string" && manifestInfo.manifest.profile.trim()
|
|
362
|
+
? manifestInfo.manifest.profile.trim()
|
|
363
|
+
: null;
|
|
364
|
+
if (!shouldUpdate) {
|
|
365
|
+
if (options.json) {
|
|
366
|
+
console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: currentProfile }, null, 2));
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
console.log(kleur.green("Project profile"));
|
|
370
|
+
console.log(`Manifest: ${manifestInfo.path}`);
|
|
371
|
+
console.log(`Profile: ${currentProfile ?? kleur.yellow("(none)")}`);
|
|
372
|
+
}
|
|
373
|
+
return { path: manifestInfo.path, profile: currentProfile };
|
|
374
|
+
}
|
|
375
|
+
const nextProfileRaw = options.unset === true ? null : typeof options.profile === "string" ? options.profile.trim() : null;
|
|
376
|
+
const nextProfile = nextProfileRaw && nextProfileRaw.length > 0 ? nextProfileRaw : null;
|
|
377
|
+
if (nextProfile) {
|
|
378
|
+
getInstafyProfileConfigPath(nextProfile);
|
|
379
|
+
}
|
|
380
|
+
const updated = { ...manifestInfo.manifest };
|
|
381
|
+
if (nextProfile) {
|
|
382
|
+
updated.profile = nextProfile;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
delete updated.profile;
|
|
386
|
+
}
|
|
387
|
+
fs.writeFileSync(manifestInfo.path, JSON.stringify(updated, null, 2), "utf8");
|
|
388
|
+
if (options.json) {
|
|
389
|
+
console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: nextProfile }, null, 2));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
console.log(kleur.green("Updated project profile."));
|
|
393
|
+
console.log(`Manifest: ${manifestInfo.path}`);
|
|
394
|
+
console.log(`Profile: ${nextProfile ?? kleur.yellow("(none)")}`);
|
|
395
|
+
}
|
|
396
|
+
return { path: manifestInfo.path, profile: nextProfile };
|
|
397
|
+
}
|
package/dist/rathole.js
CHANGED
|
@@ -52,18 +52,22 @@ export async function ensureRatholeBinary(options = {}) {
|
|
|
52
52
|
function resolveInstallPlan() {
|
|
53
53
|
const arch = process.arch;
|
|
54
54
|
const platform = process.platform;
|
|
55
|
-
if (platform === "darwin" && arch === "arm64") {
|
|
56
|
-
return { method: "cargo", binaryName: "rathole" };
|
|
57
|
-
}
|
|
58
55
|
if (platform === "darwin") {
|
|
59
|
-
if (arch
|
|
60
|
-
|
|
56
|
+
if (arch === "arm64") {
|
|
57
|
+
return {
|
|
58
|
+
method: "download",
|
|
59
|
+
assetName: "rathole-x86_64-apple-darwin.zip",
|
|
60
|
+
binaryName: "rathole",
|
|
61
|
+
};
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
if (arch === "x64") {
|
|
64
|
+
return {
|
|
65
|
+
method: "download",
|
|
66
|
+
assetName: "rathole-x86_64-apple-darwin.zip",
|
|
67
|
+
binaryName: "rathole",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Unsupported platform ${platform}/${arch} for prebuilt rathole; set RATHOLE_BIN or install via cargo.`);
|
|
67
71
|
}
|
|
68
72
|
if (platform === "linux") {
|
|
69
73
|
if (arch === "arm64") {
|
package/dist/runtime.js
CHANGED
|
@@ -7,6 +7,8 @@ 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";
|
|
11
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
10
12
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
11
13
|
const STATE_FILE = path.join(INSTAFY_DIR, "cli-runtime-state.json");
|
|
12
14
|
const LOG_DIR = path.join(INSTAFY_DIR, "cli-runtime-logs");
|
|
@@ -28,7 +30,7 @@ function resolveRuntimeBinary() {
|
|
|
28
30
|
return candidate;
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
|
-
throw new Error("runtime-agent binary not found. If you're in the instafy repo, run `cargo build -p runtime-agent`. Otherwise install Docker and rerun `instafy runtime
|
|
33
|
+
throw new Error("runtime-agent binary not found. If you're in the instafy repo, run `cargo build -p runtime-agent`. Otherwise install Docker and rerun `instafy runtime start`.");
|
|
32
34
|
}
|
|
33
35
|
function tryResolveRuntimeBinary() {
|
|
34
36
|
try {
|
|
@@ -142,28 +144,7 @@ function normalizeToken(value) {
|
|
|
142
144
|
const trimmed = value.trim();
|
|
143
145
|
return trimmed.length > 0 ? trimmed : null;
|
|
144
146
|
}
|
|
145
|
-
export
|
|
146
|
-
let current = path.resolve(startDir);
|
|
147
|
-
const root = path.parse(current).root;
|
|
148
|
-
while (true) {
|
|
149
|
-
const candidate = path.join(current, ".instafy", "project.json");
|
|
150
|
-
if (existsSync(candidate)) {
|
|
151
|
-
try {
|
|
152
|
-
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
153
|
-
if (parsed?.projectId) {
|
|
154
|
-
return { manifest: parsed, path: candidate };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
// ignore malformed manifest
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (current === root)
|
|
162
|
-
break;
|
|
163
|
-
current = path.dirname(current);
|
|
164
|
-
}
|
|
165
|
-
return { manifest: null, path: null };
|
|
166
|
-
}
|
|
147
|
+
export { findProjectManifest };
|
|
167
148
|
function readTokenFromFile(filePath) {
|
|
168
149
|
const normalized = normalizeToken(filePath);
|
|
169
150
|
if (!normalized) {
|
|
@@ -277,6 +258,12 @@ export async function mintRuntimeAccessToken(params) {
|
|
|
277
258
|
});
|
|
278
259
|
if (!response.ok) {
|
|
279
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
|
+
}
|
|
280
267
|
throw new Error(`Instafy server rejected runtime token request (${response.status} ${response.statusText}): ${text}`);
|
|
281
268
|
}
|
|
282
269
|
const payload = (await response.json());
|
|
@@ -309,7 +296,7 @@ export async function runtimeStart(options) {
|
|
|
309
296
|
const manifestInfo = findProjectManifest(process.cwd());
|
|
310
297
|
const projectId = options.project ?? env["PROJECT_ID"] ?? manifestInfo.manifest?.projectId ?? null;
|
|
311
298
|
if (!projectId) {
|
|
312
|
-
throw new Error("No project configured. Run `instafy project
|
|
299
|
+
throw new Error("No project configured. Run `instafy project init` in this folder (recommended) or pass --project.");
|
|
313
300
|
}
|
|
314
301
|
env["PROJECT_ID"] = projectId;
|
|
315
302
|
const supabaseAccessToken = resolveSupabaseAccessToken(options, env);
|