@instafy/cli 0.1.8-staging.357 → 0.1.8-staging.364
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 +1 -0
- package/dist/auth.js +33 -13
- package/dist/config.js +133 -5
- package/dist/git.js +2 -5
- package/dist/index.js +67 -2
- package/dist/project-manifest.js +24 -0
- package/dist/project.js +57 -3
- package/dist/runtime.js +2 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Run Instafy projects locally and connect them back to Instafy Studio — from an
|
|
|
9
9
|
0. Log in once: `instafy login`
|
|
10
10
|
- Opens a Studio URL; the CLI continues automatically after you sign in.
|
|
11
11
|
- Also enables Git auth (credential helper) for Instafy Git Service. Disable with `instafy login --no-git-setup`.
|
|
12
|
+
- Multiple accounts: `instafy login --profile work` (then bind folders with `instafy project:init --profile work` or `instafy project:profile work`).
|
|
12
13
|
- Optional: set defaults with `instafy config set controller-url <url>` / `instafy config set studio-url <url>`
|
|
13
14
|
1. Link a folder to a project:
|
|
14
15
|
- VS Code: install the Instafy extension and run `Instafy: Link Workspace to Project`, or
|
package/dist/auth.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as http from "node:http";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
-
import { clearInstafyCliConfig, getInstafyConfigPath, resolveConfiguredControllerUrl, resolveConfiguredStudioUrl, resolveUserAccessToken, writeInstafyCliConfig, } from "./config.js";
|
|
7
|
+
import { clearInstafyCliConfig, clearInstafyProfileConfig, getInstafyConfigPath, getInstafyProfileConfigPath, resolveConfiguredControllerUrl, resolveConfiguredStudioUrl, resolveUserAccessToken, writeInstafyCliConfig, writeInstafyProfileConfig, } from "./config.js";
|
|
8
8
|
import { installGitCredentialHelper, uninstallGitCredentialHelper } from "./git-setup.js";
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const cliVersion = (() => {
|
|
@@ -212,10 +212,11 @@ async function startCliLoginCallbackServer() {
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
export async function login(options) {
|
|
215
|
+
const profile = typeof options.profile === "string" && options.profile.trim() ? options.profile.trim() : null;
|
|
215
216
|
const explicitControllerUrl = normalizeUrl(options.controllerUrl ?? null) ??
|
|
216
217
|
normalizeUrl(process.env["INSTAFY_SERVER_URL"] ?? null) ??
|
|
217
218
|
normalizeUrl(process.env["CONTROLLER_BASE_URL"] ?? null) ??
|
|
218
|
-
(isStagingCli ? null : resolveConfiguredControllerUrl());
|
|
219
|
+
(isStagingCli ? null : resolveConfiguredControllerUrl({ profile }));
|
|
219
220
|
const defaultLocalControllerUrl = "http://127.0.0.1:8788";
|
|
220
221
|
const defaultHostedControllerUrl = "https://controller.instafy.dev";
|
|
221
222
|
const controllerUrl = explicitControllerUrl ??
|
|
@@ -226,12 +227,16 @@ export async function login(options) {
|
|
|
226
227
|
: defaultHostedControllerUrl);
|
|
227
228
|
const studioUrl = normalizeUrl(options.studioUrl ?? null) ??
|
|
228
229
|
normalizeUrl(process.env["INSTAFY_STUDIO_URL"] ?? null) ??
|
|
229
|
-
(isStagingCli ? null : resolveConfiguredStudioUrl()) ??
|
|
230
|
+
(isStagingCli ? null : resolveConfiguredStudioUrl({ profile })) ??
|
|
230
231
|
deriveDefaultStudioUrl(controllerUrl);
|
|
231
232
|
const url = new URL("/cli/login", studioUrl);
|
|
232
233
|
url.searchParams.set("serverUrl", controllerUrl);
|
|
233
234
|
if (options.json) {
|
|
234
|
-
console.log(JSON.stringify({
|
|
235
|
+
console.log(JSON.stringify({
|
|
236
|
+
url: url.toString(),
|
|
237
|
+
profile,
|
|
238
|
+
configPath: profile ? getInstafyProfileConfigPath(profile) : getInstafyConfigPath(),
|
|
239
|
+
}));
|
|
235
240
|
return;
|
|
236
241
|
}
|
|
237
242
|
let callbackServer = null;
|
|
@@ -259,7 +264,7 @@ export async function login(options) {
|
|
|
259
264
|
console.log("2) After you sign in, copy the token shown on that page.");
|
|
260
265
|
}
|
|
261
266
|
console.log("");
|
|
262
|
-
const existing = resolveUserAccessToken();
|
|
267
|
+
const existing = resolveUserAccessToken({ profile });
|
|
263
268
|
let token = provided;
|
|
264
269
|
if (!token && callbackServer) {
|
|
265
270
|
if (input.isTTY) {
|
|
@@ -337,9 +342,14 @@ export async function login(options) {
|
|
|
337
342
|
throw new Error("No token provided.");
|
|
338
343
|
}
|
|
339
344
|
if (!options.noStore) {
|
|
340
|
-
|
|
345
|
+
if (profile) {
|
|
346
|
+
writeInstafyProfileConfig(profile, { controllerUrl, studioUrl, accessToken: token });
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
writeInstafyCliConfig({ controllerUrl, studioUrl, accessToken: token });
|
|
350
|
+
}
|
|
341
351
|
console.log("");
|
|
342
|
-
console.log(kleur.green(`Saved token to ${getInstafyConfigPath()}`));
|
|
352
|
+
console.log(kleur.green(`Saved token to ${profile ? getInstafyProfileConfigPath(profile) : getInstafyConfigPath()}`));
|
|
343
353
|
if (options.gitSetup !== false) {
|
|
344
354
|
try {
|
|
345
355
|
const result = installGitCredentialHelper();
|
|
@@ -362,16 +372,26 @@ export async function login(options) {
|
|
|
362
372
|
console.log(`- ${kleur.cyan("instafy runtime:start")}`);
|
|
363
373
|
}
|
|
364
374
|
export async function logout(options) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
uninstallGitCredentialHelper();
|
|
375
|
+
if (options?.profile) {
|
|
376
|
+
clearInstafyProfileConfig(options.profile, ["accessToken"]);
|
|
368
377
|
}
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
else {
|
|
379
|
+
clearInstafyCliConfig(["accessToken"]);
|
|
380
|
+
try {
|
|
381
|
+
uninstallGitCredentialHelper();
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// ignore git helper cleanup failures
|
|
385
|
+
}
|
|
371
386
|
}
|
|
372
387
|
if (options?.json) {
|
|
373
388
|
console.log(JSON.stringify({ ok: true }));
|
|
374
389
|
return;
|
|
375
390
|
}
|
|
376
|
-
|
|
391
|
+
if (options?.profile) {
|
|
392
|
+
console.log(kleur.green(`Logged out (cleared saved access token for profile "${options.profile}").`));
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
console.log(kleur.green("Logged out (cleared saved access token)."));
|
|
396
|
+
}
|
|
377
397
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
4
5
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
5
6
|
const CONFIG_PATH = path.join(INSTAFY_DIR, "config.json");
|
|
7
|
+
const PROFILES_DIR = path.join(INSTAFY_DIR, "profiles");
|
|
6
8
|
function normalizeToken(value) {
|
|
7
9
|
if (typeof value !== "string") {
|
|
8
10
|
return null;
|
|
@@ -24,9 +26,45 @@ function normalizeUrl(value) {
|
|
|
24
26
|
}
|
|
25
27
|
return trimmed.replace(/\/$/, "");
|
|
26
28
|
}
|
|
29
|
+
function normalizeProfileName(value) {
|
|
30
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(trimmed)) {
|
|
35
|
+
throw new Error(`Invalid profile name "${value}". Use letters/numbers plus . _ - (max 64 chars).`);
|
|
36
|
+
}
|
|
37
|
+
if (trimmed.includes("..")) {
|
|
38
|
+
throw new Error(`Invalid profile name "${value}".`);
|
|
39
|
+
}
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
27
42
|
export function getInstafyConfigPath() {
|
|
28
43
|
return CONFIG_PATH;
|
|
29
44
|
}
|
|
45
|
+
export function getInstafyProfilesDirPath() {
|
|
46
|
+
return PROFILES_DIR;
|
|
47
|
+
}
|
|
48
|
+
export function getInstafyProfileConfigPath(profile) {
|
|
49
|
+
const normalized = normalizeProfileName(profile);
|
|
50
|
+
if (!normalized) {
|
|
51
|
+
throw new Error("Profile name is required.");
|
|
52
|
+
}
|
|
53
|
+
return path.join(PROFILES_DIR, `${normalized}.json`);
|
|
54
|
+
}
|
|
55
|
+
export function listInstafyProfileNames() {
|
|
56
|
+
try {
|
|
57
|
+
const entries = fs.readdirSync(PROFILES_DIR, { withFileTypes: true });
|
|
58
|
+
return entries
|
|
59
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
60
|
+
.map((entry) => entry.name.slice(0, -".json".length))
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.sort((a, b) => a.localeCompare(b));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
30
68
|
export function readInstafyCliConfig() {
|
|
31
69
|
try {
|
|
32
70
|
const raw = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
@@ -46,6 +84,26 @@ export function readInstafyCliConfig() {
|
|
|
46
84
|
return {};
|
|
47
85
|
}
|
|
48
86
|
}
|
|
87
|
+
export function readInstafyProfileConfig(profile) {
|
|
88
|
+
const filePath = getInstafyProfileConfigPath(profile);
|
|
89
|
+
try {
|
|
90
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
91
|
+
const parsed = JSON.parse(raw);
|
|
92
|
+
if (!parsed || typeof parsed !== "object") {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
const record = parsed;
|
|
96
|
+
return {
|
|
97
|
+
controllerUrl: normalizeUrl(typeof record.controllerUrl === "string" ? record.controllerUrl : null),
|
|
98
|
+
studioUrl: normalizeUrl(typeof record.studioUrl === "string" ? record.studioUrl : null),
|
|
99
|
+
accessToken: normalizeToken(typeof record.accessToken === "string" ? record.accessToken : null),
|
|
100
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
49
107
|
export function writeInstafyCliConfig(update) {
|
|
50
108
|
const existing = readInstafyCliConfig();
|
|
51
109
|
const next = {
|
|
@@ -64,6 +122,25 @@ export function writeInstafyCliConfig(update) {
|
|
|
64
122
|
}
|
|
65
123
|
return next;
|
|
66
124
|
}
|
|
125
|
+
export function writeInstafyProfileConfig(profile, update) {
|
|
126
|
+
const filePath = getInstafyProfileConfigPath(profile);
|
|
127
|
+
const existing = readInstafyProfileConfig(profile);
|
|
128
|
+
const next = {
|
|
129
|
+
controllerUrl: normalizeUrl(update.controllerUrl ?? existing.controllerUrl ?? null),
|
|
130
|
+
studioUrl: normalizeUrl(update.studioUrl ?? existing.studioUrl ?? null),
|
|
131
|
+
accessToken: normalizeToken(update.accessToken ?? existing.accessToken ?? null),
|
|
132
|
+
updatedAt: new Date().toISOString(),
|
|
133
|
+
};
|
|
134
|
+
fs.mkdirSync(PROFILES_DIR, { recursive: true });
|
|
135
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), { encoding: "utf8" });
|
|
136
|
+
try {
|
|
137
|
+
fs.chmodSync(filePath, 0o600);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// ignore chmod failures (windows / unusual fs)
|
|
141
|
+
}
|
|
142
|
+
return next;
|
|
143
|
+
}
|
|
67
144
|
export function clearInstafyCliConfig(keys) {
|
|
68
145
|
if (!keys || keys.length === 0) {
|
|
69
146
|
try {
|
|
@@ -81,20 +158,70 @@ export function clearInstafyCliConfig(keys) {
|
|
|
81
158
|
}
|
|
82
159
|
writeInstafyCliConfig(next);
|
|
83
160
|
}
|
|
84
|
-
export function
|
|
161
|
+
export function clearInstafyProfileConfig(profile, keys) {
|
|
162
|
+
const filePath = getInstafyProfileConfigPath(profile);
|
|
163
|
+
if (!keys || keys.length === 0) {
|
|
164
|
+
try {
|
|
165
|
+
fs.rmSync(filePath, { force: true });
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// ignore
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const existing = readInstafyProfileConfig(profile);
|
|
173
|
+
const next = { ...existing };
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
next[key] = null;
|
|
176
|
+
}
|
|
177
|
+
writeInstafyProfileConfig(profile, next);
|
|
178
|
+
}
|
|
179
|
+
export function resolveActiveProfileName(params) {
|
|
180
|
+
const explicit = normalizeProfileName(params?.profile ?? null);
|
|
181
|
+
if (explicit) {
|
|
182
|
+
return explicit;
|
|
183
|
+
}
|
|
184
|
+
const fromEnv = normalizeProfileName(process.env["INSTAFY_PROFILE"] ?? null);
|
|
185
|
+
if (fromEnv) {
|
|
186
|
+
return fromEnv;
|
|
187
|
+
}
|
|
188
|
+
const cwd = (params?.cwd ?? process.cwd()).trim();
|
|
189
|
+
if (!cwd) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const manifest = findProjectManifest(cwd).manifest;
|
|
193
|
+
return normalizeProfileName(manifest?.profile ?? null);
|
|
194
|
+
}
|
|
195
|
+
export function resolveConfiguredControllerUrl(params) {
|
|
196
|
+
const profile = resolveActiveProfileName(params);
|
|
197
|
+
if (profile) {
|
|
198
|
+
const configured = readInstafyProfileConfig(profile);
|
|
199
|
+
return normalizeUrl(configured.controllerUrl ?? null);
|
|
200
|
+
}
|
|
85
201
|
const config = readInstafyCliConfig();
|
|
86
202
|
return normalizeUrl(config.controllerUrl ?? null);
|
|
87
203
|
}
|
|
88
|
-
export function resolveConfiguredStudioUrl() {
|
|
204
|
+
export function resolveConfiguredStudioUrl(params) {
|
|
205
|
+
const profile = resolveActiveProfileName(params);
|
|
206
|
+
if (profile) {
|
|
207
|
+
const configured = readInstafyProfileConfig(profile);
|
|
208
|
+
return normalizeUrl(configured.studioUrl ?? null);
|
|
209
|
+
}
|
|
89
210
|
const config = readInstafyCliConfig();
|
|
90
211
|
return normalizeUrl(config.studioUrl ?? null);
|
|
91
212
|
}
|
|
92
|
-
export function resolveConfiguredAccessToken() {
|
|
213
|
+
export function resolveConfiguredAccessToken(params) {
|
|
214
|
+
const profile = resolveActiveProfileName(params);
|
|
215
|
+
if (profile) {
|
|
216
|
+
const configured = readInstafyProfileConfig(profile);
|
|
217
|
+
return normalizeToken(configured.accessToken ?? null);
|
|
218
|
+
}
|
|
93
219
|
const config = readInstafyCliConfig();
|
|
94
220
|
return normalizeToken(config.accessToken ?? null);
|
|
95
221
|
}
|
|
96
222
|
export function resolveControllerUrl(params) {
|
|
97
|
-
const
|
|
223
|
+
const profile = resolveActiveProfileName({ profile: params?.profile ?? null, cwd: params?.cwd ?? null });
|
|
224
|
+
const config = profile ? readInstafyProfileConfig(profile) : readInstafyCliConfig();
|
|
98
225
|
return (normalizeUrl(params?.controllerUrl ?? null) ??
|
|
99
226
|
normalizeUrl(process.env["INSTAFY_SERVER_URL"] ?? null) ??
|
|
100
227
|
normalizeUrl(process.env["CONTROLLER_BASE_URL"] ?? null) ??
|
|
@@ -102,7 +229,8 @@ export function resolveControllerUrl(params) {
|
|
|
102
229
|
"http://127.0.0.1:8788");
|
|
103
230
|
}
|
|
104
231
|
export function resolveUserAccessToken(params) {
|
|
105
|
-
const
|
|
232
|
+
const profile = resolveActiveProfileName({ profile: params?.profile ?? null, cwd: params?.cwd ?? null });
|
|
233
|
+
const config = profile ? readInstafyProfileConfig(profile) : readInstafyCliConfig();
|
|
106
234
|
return (normalizeToken(params?.accessToken ?? null) ??
|
|
107
235
|
normalizeToken(process.env["INSTAFY_ACCESS_TOKEN"] ?? null) ??
|
|
108
236
|
normalizeToken(process.env["CONTROLLER_ACCESS_TOKEN"] ?? null) ??
|
package/dist/git.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
|
-
import { resolveConfiguredAccessToken } from "./config.js";
|
|
4
|
+
import { resolveConfiguredAccessToken, resolveControllerUrl } from "./config.js";
|
|
5
5
|
function normalizeToken(value) {
|
|
6
6
|
if (typeof value !== "string") {
|
|
7
7
|
return null;
|
|
@@ -68,10 +68,7 @@ export async function mintGitAccessToken(params) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
export async function gitToken(options) {
|
|
71
|
-
const controllerUrl = options.controllerUrl ??
|
|
72
|
-
process.env["INSTAFY_SERVER_URL"] ??
|
|
73
|
-
process.env["CONTROLLER_BASE_URL"] ??
|
|
74
|
-
"http://127.0.0.1:8788";
|
|
71
|
+
const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
|
|
75
72
|
const token = resolveControllerAccessToken({
|
|
76
73
|
controllerAccessToken: options.controllerAccessToken,
|
|
77
74
|
supabaseAccessToken: options.supabaseAccessToken,
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,11 @@ import { runtimeStart, runtimeStatus, runtimeStop, runtimeToken, findProjectMani
|
|
|
6
6
|
import { login, logout } from "./auth.js";
|
|
7
7
|
import { gitToken } from "./git.js";
|
|
8
8
|
import { runGitCredentialHelper } from "./git-credential.js";
|
|
9
|
-
import { projectInit } from "./project.js";
|
|
9
|
+
import { projectInit, projectProfile } from "./project.js";
|
|
10
10
|
import { listTunnelSessions, startTunnelDetached, stopTunnelSession, tailTunnelLogs, runTunnelCommand } from "./tunnel.js";
|
|
11
11
|
import { requestControllerApi } from "./api.js";
|
|
12
12
|
import { configGet, configList, configPath, configSet, configUnset } from "./config-command.js";
|
|
13
|
+
import { getInstafyProfileConfigPath, listInstafyProfileNames, readInstafyProfileConfig } from "./config.js";
|
|
13
14
|
export const program = new Command();
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
const pkg = require("../package.json");
|
|
@@ -40,6 +41,7 @@ program
|
|
|
40
41
|
.description("Log in and save an access token for future CLI commands")
|
|
41
42
|
.option("--studio-url <url>", "Studio web URL (default: staging or localhost)")
|
|
42
43
|
.option("--server-url <url>", "Instafy server/controller URL")
|
|
44
|
+
.option("--profile <name>", "Save token under a named profile (multi-account support)")
|
|
43
45
|
.option("--token <token>", "Provide token directly (skips prompt)")
|
|
44
46
|
.option("--no-git-setup", "Do not configure git credential helper")
|
|
45
47
|
.option("--no-store", "Do not save token to ~/.instafy/config.json")
|
|
@@ -52,6 +54,7 @@ program
|
|
|
52
54
|
token: opts.token,
|
|
53
55
|
gitSetup: opts.gitSetup,
|
|
54
56
|
noStore: opts.store === false,
|
|
57
|
+
profile: opts.profile,
|
|
55
58
|
json: opts.json,
|
|
56
59
|
});
|
|
57
60
|
}
|
|
@@ -63,10 +66,49 @@ program
|
|
|
63
66
|
program
|
|
64
67
|
.command("logout")
|
|
65
68
|
.description("Clear the saved CLI access token")
|
|
69
|
+
.option("--profile <name>", "Clear the token for a named profile")
|
|
66
70
|
.option("--json", "Output JSON")
|
|
67
71
|
.action(async (opts) => {
|
|
68
72
|
try {
|
|
69
|
-
await logout({ json: opts.json });
|
|
73
|
+
await logout({ json: opts.json, profile: opts.profile });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(kleur.red(String(error)));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
program
|
|
81
|
+
.command("profile:list")
|
|
82
|
+
.description("List saved CLI profiles (~/.instafy/profiles)")
|
|
83
|
+
.option("--json", "Output JSON")
|
|
84
|
+
.action(async (opts) => {
|
|
85
|
+
try {
|
|
86
|
+
const names = listInstafyProfileNames();
|
|
87
|
+
const profiles = names.map((name) => {
|
|
88
|
+
const config = readInstafyProfileConfig(name);
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
path: getInstafyProfileConfigPath(name),
|
|
92
|
+
controllerUrl: config.controllerUrl ?? null,
|
|
93
|
+
studioUrl: config.studioUrl ?? null,
|
|
94
|
+
accessTokenSet: Boolean(config.accessToken),
|
|
95
|
+
updatedAt: config.updatedAt ?? null,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
if (opts.json) {
|
|
99
|
+
console.log(JSON.stringify({ profiles }, null, 2));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (profiles.length === 0) {
|
|
103
|
+
console.log(kleur.yellow("No profiles found."));
|
|
104
|
+
console.log(`Create one with: ${kleur.cyan("instafy login --profile <name>")}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
console.log(kleur.green("Instafy CLI profiles"));
|
|
108
|
+
for (const profile of profiles) {
|
|
109
|
+
const token = profile.accessTokenSet ? kleur.green("token") : kleur.yellow("no-token");
|
|
110
|
+
console.log(`- ${kleur.cyan(profile.name)} (${token})`);
|
|
111
|
+
}
|
|
70
112
|
}
|
|
71
113
|
catch (error) {
|
|
72
114
|
console.error(kleur.red(String(error)));
|
|
@@ -80,6 +122,7 @@ const projectInitCommand = program
|
|
|
80
122
|
addServerUrlOptions(projectInitCommand);
|
|
81
123
|
projectInitCommand
|
|
82
124
|
.option("--access-token <token>", "Instafy or Supabase access token")
|
|
125
|
+
.option("--profile <name>", "CLI profile to use when running commands in this folder")
|
|
83
126
|
.option("--project-type <type>", "Project type (customer|sandbox)")
|
|
84
127
|
.option("--org-id <uuid>", "Optional organization id")
|
|
85
128
|
.option("--org-name <name>", "Optional organization name")
|
|
@@ -92,6 +135,7 @@ projectInitCommand
|
|
|
92
135
|
path: opts.path,
|
|
93
136
|
controllerUrl: opts.serverUrl ?? opts.controllerUrl,
|
|
94
137
|
accessToken: opts.accessToken,
|
|
138
|
+
profile: opts.profile,
|
|
95
139
|
projectType: opts.projectType,
|
|
96
140
|
orgId: opts.orgId,
|
|
97
141
|
orgName: opts.orgName,
|
|
@@ -105,6 +149,27 @@ projectInitCommand
|
|
|
105
149
|
process.exit(1);
|
|
106
150
|
}
|
|
107
151
|
});
|
|
152
|
+
program
|
|
153
|
+
.command("project:profile")
|
|
154
|
+
.description("Get/set the CLI profile for this folder (.instafy/project.json)")
|
|
155
|
+
.argument("[profile]", "Profile name to set (omit to print current)")
|
|
156
|
+
.option("--unset", "Clear the configured profile for this folder")
|
|
157
|
+
.option("--path <dir>", "Directory to search for the manifest (default: cwd)")
|
|
158
|
+
.option("--json", "Output JSON")
|
|
159
|
+
.action(async (profile, opts) => {
|
|
160
|
+
try {
|
|
161
|
+
projectProfile({
|
|
162
|
+
profile,
|
|
163
|
+
unset: opts.unset,
|
|
164
|
+
path: opts.path,
|
|
165
|
+
json: opts.json,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error(kleur.red(String(error)));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
108
173
|
const configCommand = program.command("config").description("Get/set saved CLI configuration");
|
|
109
174
|
configCommand
|
|
110
175
|
.command("path")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function findProjectManifest(startDir) {
|
|
4
|
+
let current = path.resolve(startDir);
|
|
5
|
+
const root = path.parse(current).root;
|
|
6
|
+
while (true) {
|
|
7
|
+
const candidate = path.join(current, ".instafy", "project.json");
|
|
8
|
+
if (existsSync(candidate)) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
11
|
+
if (parsed?.projectId) {
|
|
12
|
+
return { manifest: parsed, path: candidate };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// ignore malformed manifest
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (current === root)
|
|
20
|
+
break;
|
|
21
|
+
current = path.dirname(current);
|
|
22
|
+
}
|
|
23
|
+
return { manifest: null, path: null };
|
|
24
|
+
}
|
package/dist/project.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
|
-
import { resolveControllerUrl, resolveUserAccessToken } from "./config.js";
|
|
4
|
+
import { getInstafyProfileConfigPath, resolveControllerUrl, resolveUserAccessToken } from "./config.js";
|
|
5
5
|
import { formatAuthRequiredError } from "./errors.js";
|
|
6
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
6
7
|
async function fetchOrganizations(controllerUrl, token) {
|
|
7
8
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
8
9
|
headers: {
|
|
@@ -111,8 +112,16 @@ export async function listProjects(options) {
|
|
|
111
112
|
}
|
|
112
113
|
export async function projectInit(options) {
|
|
113
114
|
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
114
|
-
const controllerUrl = resolveControllerUrl({
|
|
115
|
-
|
|
115
|
+
const controllerUrl = resolveControllerUrl({
|
|
116
|
+
controllerUrl: options.controllerUrl ?? null,
|
|
117
|
+
profile: options.profile ?? null,
|
|
118
|
+
cwd: rootDir,
|
|
119
|
+
});
|
|
120
|
+
const token = resolveUserAccessToken({
|
|
121
|
+
accessToken: options.accessToken ?? null,
|
|
122
|
+
profile: options.profile ?? null,
|
|
123
|
+
cwd: rootDir,
|
|
124
|
+
});
|
|
116
125
|
if (!token) {
|
|
117
126
|
throw formatAuthRequiredError({
|
|
118
127
|
retryCommand: "instafy project:init",
|
|
@@ -146,6 +155,7 @@ export async function projectInit(options) {
|
|
|
146
155
|
orgId: json.org_id ?? org.orgId ?? null,
|
|
147
156
|
orgName: json.org_name ?? org.orgName ?? null,
|
|
148
157
|
controllerUrl,
|
|
158
|
+
profile: options.profile ?? null,
|
|
149
159
|
createdAt: new Date().toISOString(),
|
|
150
160
|
}, null, 2), "utf8");
|
|
151
161
|
}
|
|
@@ -171,3 +181,47 @@ export async function projectInit(options) {
|
|
|
171
181
|
}
|
|
172
182
|
return json;
|
|
173
183
|
}
|
|
184
|
+
export function projectProfile(options) {
|
|
185
|
+
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
186
|
+
const manifestInfo = findProjectManifest(rootDir);
|
|
187
|
+
if (!manifestInfo.path || !manifestInfo.manifest) {
|
|
188
|
+
throw new Error("No project configured. Run `instafy project:init` in this folder first.");
|
|
189
|
+
}
|
|
190
|
+
const shouldUpdate = options.unset === true || typeof options.profile === "string";
|
|
191
|
+
const currentProfile = typeof manifestInfo.manifest.profile === "string" && manifestInfo.manifest.profile.trim()
|
|
192
|
+
? manifestInfo.manifest.profile.trim()
|
|
193
|
+
: null;
|
|
194
|
+
if (!shouldUpdate) {
|
|
195
|
+
if (options.json) {
|
|
196
|
+
console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: currentProfile }, null, 2));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(kleur.green("Project profile"));
|
|
200
|
+
console.log(`Manifest: ${manifestInfo.path}`);
|
|
201
|
+
console.log(`Profile: ${currentProfile ?? kleur.yellow("(none)")}`);
|
|
202
|
+
}
|
|
203
|
+
return { path: manifestInfo.path, profile: currentProfile };
|
|
204
|
+
}
|
|
205
|
+
const nextProfileRaw = options.unset === true ? null : typeof options.profile === "string" ? options.profile.trim() : null;
|
|
206
|
+
const nextProfile = nextProfileRaw && nextProfileRaw.length > 0 ? nextProfileRaw : null;
|
|
207
|
+
if (nextProfile) {
|
|
208
|
+
getInstafyProfileConfigPath(nextProfile);
|
|
209
|
+
}
|
|
210
|
+
const updated = { ...manifestInfo.manifest };
|
|
211
|
+
if (nextProfile) {
|
|
212
|
+
updated.profile = nextProfile;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
delete updated.profile;
|
|
216
|
+
}
|
|
217
|
+
fs.writeFileSync(manifestInfo.path, JSON.stringify(updated, null, 2), "utf8");
|
|
218
|
+
if (options.json) {
|
|
219
|
+
console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: nextProfile }, null, 2));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(kleur.green("Updated project profile."));
|
|
223
|
+
console.log(`Manifest: ${manifestInfo.path}`);
|
|
224
|
+
console.log(`Profile: ${nextProfile ?? kleur.yellow("(none)")}`);
|
|
225
|
+
}
|
|
226
|
+
return { path: manifestInfo.path, profile: nextProfile };
|
|
227
|
+
}
|
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 { findProjectManifest } from "./project-manifest.js";
|
|
10
11
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
11
12
|
const STATE_FILE = path.join(INSTAFY_DIR, "cli-runtime-state.json");
|
|
12
13
|
const LOG_DIR = path.join(INSTAFY_DIR, "cli-runtime-logs");
|
|
@@ -142,28 +143,7 @@ function normalizeToken(value) {
|
|
|
142
143
|
const trimmed = value.trim();
|
|
143
144
|
return trimmed.length > 0 ? trimmed : null;
|
|
144
145
|
}
|
|
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
|
-
}
|
|
146
|
+
export { findProjectManifest };
|
|
167
147
|
function readTokenFromFile(filePath) {
|
|
168
148
|
const normalized = normalizeToken(filePath);
|
|
169
149
|
if (!normalized) {
|
package/package.json
CHANGED