apiblaze 0.12.1 → 0.15.0
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 +915 -762
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -51,6 +51,14 @@ var init_types = __esm({
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
// src/lib/auth.ts
|
|
54
|
+
var auth_exports = {};
|
|
55
|
+
__export(auth_exports, {
|
|
56
|
+
clearCredentials: () => clearCredentials,
|
|
57
|
+
getAccessToken: () => getAccessToken,
|
|
58
|
+
getApiblazeDir: () => getApiblazeDir,
|
|
59
|
+
loadCredentials: () => loadCredentials,
|
|
60
|
+
saveCredentials: () => saveCredentials
|
|
61
|
+
});
|
|
54
62
|
function saveCredentials(creds) {
|
|
55
63
|
fs.mkdirSync(APIBLAZE_DIR, { recursive: true });
|
|
56
64
|
fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
|
|
@@ -82,6 +90,9 @@ function getAccessToken() {
|
|
|
82
90
|
}
|
|
83
91
|
return creds.accessToken;
|
|
84
92
|
}
|
|
93
|
+
function getApiblazeDir() {
|
|
94
|
+
return APIBLAZE_DIR;
|
|
95
|
+
}
|
|
85
96
|
var fs, os, path, APIBLAZE_DIR, CREDENTIALS_PATH;
|
|
86
97
|
var init_auth = __esm({
|
|
87
98
|
"src/lib/auth.ts"() {
|
|
@@ -232,6 +243,65 @@ var init_api = __esm({
|
|
|
232
243
|
}
|
|
233
244
|
});
|
|
234
245
|
|
|
246
|
+
// src/lib/team.ts
|
|
247
|
+
var team_exports = {};
|
|
248
|
+
__export(team_exports, {
|
|
249
|
+
resolveLinkedTeam: () => resolveLinkedTeam
|
|
250
|
+
});
|
|
251
|
+
async function resolveLinkedTeam(opts) {
|
|
252
|
+
let teams;
|
|
253
|
+
try {
|
|
254
|
+
teams = await getTeams();
|
|
255
|
+
} catch {
|
|
256
|
+
return opts.preferredId ? { teamId: opts.preferredId } : null;
|
|
257
|
+
}
|
|
258
|
+
if (teams.length === 0) return null;
|
|
259
|
+
const preferred = opts.preferredId ? teams.find((t) => t.teamId === opts.preferredId) : void 0;
|
|
260
|
+
if (preferred && !(opts.promptWhenMultiple && opts.interactive && teams.length > 1)) {
|
|
261
|
+
return { teamId: preferred.teamId, teamName: preferred.name };
|
|
262
|
+
}
|
|
263
|
+
if (preferred) {
|
|
264
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
265
|
+
const { chosen: chosen2 } = await inquirer3.prompt([{
|
|
266
|
+
type: "list",
|
|
267
|
+
name: "chosen",
|
|
268
|
+
message: "Which team do you want to work in?",
|
|
269
|
+
default: preferred.teamId,
|
|
270
|
+
choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
|
|
271
|
+
}]);
|
|
272
|
+
const picked2 = teams.find((t) => t.teamId === chosen2);
|
|
273
|
+
return { teamId: picked2.teamId, teamName: picked2.name };
|
|
274
|
+
}
|
|
275
|
+
if (opts.preferredId) {
|
|
276
|
+
console.log(import_chalk.default.yellow("\nYour previously linked team is no longer available."));
|
|
277
|
+
}
|
|
278
|
+
if (teams.length === 1) {
|
|
279
|
+
console.log(`${import_chalk.default.cyan("\u2192")} Linking to your team ${import_chalk.default.bold(teams[0].name)}.`);
|
|
280
|
+
return { teamId: teams[0].teamId, teamName: teams[0].name };
|
|
281
|
+
}
|
|
282
|
+
if (!opts.interactive) {
|
|
283
|
+
console.log(import_chalk.default.yellow(`Linking to "${teams[0].name}" \u2014 pass --team to choose another.`));
|
|
284
|
+
return { teamId: teams[0].teamId, teamName: teams[0].name };
|
|
285
|
+
}
|
|
286
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
287
|
+
const { chosen } = await inquirer2.prompt([{
|
|
288
|
+
type: "list",
|
|
289
|
+
name: "chosen",
|
|
290
|
+
message: "Which team do you want to link to?",
|
|
291
|
+
choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
|
|
292
|
+
}]);
|
|
293
|
+
const picked = teams.find((t) => t.teamId === chosen);
|
|
294
|
+
return { teamId: picked.teamId, teamName: picked.name };
|
|
295
|
+
}
|
|
296
|
+
var import_chalk;
|
|
297
|
+
var init_team = __esm({
|
|
298
|
+
"src/lib/team.ts"() {
|
|
299
|
+
"use strict";
|
|
300
|
+
import_chalk = __toESM(require("chalk"));
|
|
301
|
+
init_api();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
235
305
|
// src/lib/anon-cred.ts
|
|
236
306
|
var anon_cred_exports = {};
|
|
237
307
|
__export(anon_cred_exports, {
|
|
@@ -300,180 +370,545 @@ var init_anon_cred = __esm({
|
|
|
300
370
|
}
|
|
301
371
|
});
|
|
302
372
|
|
|
303
|
-
// src/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (verbose) entries.push(e);
|
|
309
|
-
}
|
|
310
|
-
function maskBody(body) {
|
|
311
|
-
if (body === void 0) return void 0;
|
|
312
|
-
return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
|
|
313
|
-
}
|
|
314
|
-
function renderTrace() {
|
|
315
|
-
if (!verbose || entries.length === 0) return;
|
|
316
|
-
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
317
|
-
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
318
|
-
console.log(
|
|
319
|
-
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
|
|
320
|
-
);
|
|
321
|
-
entries.forEach((e, i) => {
|
|
322
|
-
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
323
|
-
if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
|
|
324
|
-
const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
|
|
325
|
-
const masked = maskBody(e.body);
|
|
326
|
-
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
327
|
-
console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
|
|
328
|
-
console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
|
|
329
|
-
if (hasBody) {
|
|
330
|
-
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
331
|
-
console.log(import_chalk15.default.green(` -d '${masked}'`));
|
|
332
|
-
}
|
|
333
|
-
if (i < entries.length - 1) console.log();
|
|
334
|
-
});
|
|
335
|
-
entries.length = 0;
|
|
336
|
-
}
|
|
337
|
-
var import_chalk15, CONTROL_API_VERSION, verbose, entries, SECRET_KEY;
|
|
338
|
-
var init_trace = __esm({
|
|
339
|
-
"src/lib/trace.ts"() {
|
|
340
|
-
"use strict";
|
|
341
|
-
import_chalk15 = __toESM(require("chalk"));
|
|
342
|
-
CONTROL_API_VERSION = "1.0.0";
|
|
343
|
-
verbose = false;
|
|
344
|
-
entries = [];
|
|
345
|
-
SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
|
|
346
|
-
}
|
|
373
|
+
// src/commands/create.ts
|
|
374
|
+
var create_exports = {};
|
|
375
|
+
__export(create_exports, {
|
|
376
|
+
buildTryItCurl: () => buildTryItCurl,
|
|
377
|
+
runCreate: () => runCreate
|
|
347
378
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
|
|
353
|
-
method: "POST",
|
|
354
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
355
|
-
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
356
|
-
});
|
|
357
|
-
let data = null;
|
|
379
|
+
function normalizeName(raw) {
|
|
380
|
+
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
381
|
+
}
|
|
382
|
+
function isHttpUrl(s) {
|
|
358
383
|
try {
|
|
359
|
-
|
|
384
|
+
const u = new URL((s || "").trim());
|
|
385
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
360
386
|
} catch {
|
|
387
|
+
return false;
|
|
361
388
|
}
|
|
362
|
-
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
363
|
-
maybePrintBilling(data);
|
|
364
|
-
if (!res.ok) {
|
|
365
|
-
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
366
|
-
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
367
|
-
}
|
|
368
|
-
return data;
|
|
369
389
|
}
|
|
370
|
-
function
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
390
|
+
function stripTenantFromPortal(devPortal) {
|
|
391
|
+
try {
|
|
392
|
+
const u = new URL(devPortal);
|
|
393
|
+
const dot = u.hostname.indexOf(".");
|
|
394
|
+
if (dot < 0) return devPortal;
|
|
395
|
+
const product = u.hostname.slice(0, dot).split("-")[0];
|
|
396
|
+
u.hostname = `${product}${u.hostname.slice(dot)}`;
|
|
397
|
+
return u.toString();
|
|
398
|
+
} catch {
|
|
399
|
+
return devPortal;
|
|
376
400
|
}
|
|
377
401
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
402
|
+
function fail(message) {
|
|
403
|
+
console.error(import_chalk6.default.red(`Error: ${message}`));
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
function buildTryItCurl(url, authType, apiKey) {
|
|
407
|
+
if (authType === "api_key") {
|
|
408
|
+
if (!apiKey) return null;
|
|
409
|
+
return `curl ${url} -H "X-API-Key: ${apiKey}"`;
|
|
387
410
|
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// src/lib/tenant-pick.ts
|
|
391
|
-
var tenant_pick_exports = {};
|
|
392
|
-
__export(tenant_pick_exports, {
|
|
393
|
-
pickTenant: () => pickTenant
|
|
394
|
-
});
|
|
395
|
-
async function fetchPage(teamId, q) {
|
|
396
|
-
const out = await admin({
|
|
397
|
-
method: "GET",
|
|
398
|
-
path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1&limit=${PAGE}${q ? `&q=${encodeURIComponent(q)}` : ""}`,
|
|
399
|
-
summary: q ? `Search tenants matching "${q}"` : "List tenants (first page)"
|
|
400
|
-
});
|
|
401
|
-
const rows = (out?.tenants ?? []).map(
|
|
402
|
-
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
403
|
-
);
|
|
404
|
-
return { rows, total: out?.total ?? rows.length, defaultTenant: out?.default_tenant ?? null };
|
|
411
|
+
if (authType === "none") return `curl ${url}`;
|
|
412
|
+
return null;
|
|
405
413
|
}
|
|
406
|
-
function
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
414
|
+
function printCurlExample(url, authType, apiKey, devPortal) {
|
|
415
|
+
const curl = buildTryItCurl(url, authType, apiKey);
|
|
416
|
+
console.log();
|
|
417
|
+
if (curl) {
|
|
418
|
+
console.log(` ${import_chalk6.default.dim("Try it \u2014 copy/paste:")}`);
|
|
419
|
+
console.log(` ${import_chalk6.default.cyan(curl)}`);
|
|
420
|
+
} else if (authType === "oauth") {
|
|
421
|
+
console.log(` ${import_chalk6.default.dim("Try it:")} this proxy uses OAuth \u2014 sign in at ${import_chalk6.default.bold(devPortal ?? "the dev portal")} to get a token,`);
|
|
422
|
+
console.log(` ${import_chalk6.default.dim(`then call ${url} with`)} ${import_chalk6.default.cyan('-H "Authorization: Bearer <token>"')}`);
|
|
423
|
+
}
|
|
413
424
|
}
|
|
414
|
-
async function
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
+
async function runCreate(opts = {}) {
|
|
426
|
+
const creds = loadCredentials();
|
|
427
|
+
if (!creds) {
|
|
428
|
+
await runAnonymousCreate(opts);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
432
|
+
const auth = (opts.auth ?? "api_key").toLowerCase();
|
|
433
|
+
if (!VALID_AUTH.includes(auth)) {
|
|
434
|
+
fail(`Invalid --auth "${auth}". Use one of: ${VALID_AUTH.join(", ")}.`);
|
|
435
|
+
}
|
|
436
|
+
let teamId = creds.teamId;
|
|
437
|
+
if (opts.team) {
|
|
438
|
+
if (opts.team.startsWith("team_")) {
|
|
439
|
+
teamId = opts.team;
|
|
440
|
+
} else {
|
|
441
|
+
const teams = await getTeams().catch(() => []);
|
|
442
|
+
const match = teams.find(
|
|
443
|
+
(t) => t.teamId === opts.team || t.name.toLowerCase() === opts.team.toLowerCase()
|
|
444
|
+
);
|
|
445
|
+
if (!match) {
|
|
446
|
+
fail(`Team "${opts.team}" not found. Run \`apiblaze team\` to see your teams.`);
|
|
425
447
|
}
|
|
426
|
-
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
const truncated = page.total > page.rows.length;
|
|
430
|
-
const choices = page.rows.map((t) => ({
|
|
431
|
-
name: label(t, page.defaultTenant, active),
|
|
432
|
-
value: t.tenant_name
|
|
433
|
-
}));
|
|
434
|
-
if (truncated || q) {
|
|
435
|
-
choices.push(new inquirer2.Separator(import_chalk22.default.dim(
|
|
436
|
-
truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
|
|
437
|
-
)));
|
|
438
|
-
choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
|
|
439
|
-
}
|
|
440
|
-
if (q) choices.push({ name: "\u21BA Show all (clear search)", value: "\0clear" });
|
|
441
|
-
if (opts.allowCreate) choices.push({ name: "\uFF0B Create a new tenant\u2026", value: "\0create" });
|
|
442
|
-
if (opts.allowBack) choices.push({ name: "\u2190 Back", value: "\0back" });
|
|
443
|
-
const { picked } = await inquirer2.prompt([{
|
|
444
|
-
type: "list",
|
|
445
|
-
name: "picked",
|
|
446
|
-
pageSize: PAGE + 5,
|
|
447
|
-
message: opts.message ?? "Which tenant?",
|
|
448
|
-
default: active && page.rows.some((t) => t.tenant_name === active) ? active : void 0,
|
|
449
|
-
choices
|
|
450
|
-
}]);
|
|
451
|
-
if (picked === "\0back") return null;
|
|
452
|
-
if (picked === "\0clear") {
|
|
453
|
-
q = "";
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (picked === "\0create") return await createTenantInline(teamId);
|
|
457
|
-
if (picked === "\0search") {
|
|
458
|
-
const { nq } = await inquirer2.prompt([{ type: "input", name: "nq", message: "Search (name or display name):", default: q }]);
|
|
459
|
-
q = String(nq ?? "").trim();
|
|
460
|
-
continue;
|
|
448
|
+
teamId = match.teamId;
|
|
461
449
|
}
|
|
462
|
-
return picked;
|
|
463
450
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
451
|
+
if (!opts.json) console.log(import_chalk6.default.bold("\nCreate an API proxy\n"));
|
|
452
|
+
let name = "";
|
|
453
|
+
if (opts.name !== void 0) {
|
|
454
|
+
name = normalizeName(opts.name);
|
|
455
|
+
if (name.length < 3) fail("Proxy name must be at least 3 characters (letters and digits only).");
|
|
456
|
+
const check = await checkProxyName(name, teamId, opts.apiversion).catch(() => null);
|
|
457
|
+
if (check && (!check.canUseProjectName || !check.canUseApiVersion)) {
|
|
458
|
+
fail(`Proxy name "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}.`);
|
|
459
|
+
}
|
|
460
|
+
} else if (interactive) {
|
|
461
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
462
|
+
for (; ; ) {
|
|
463
|
+
const { rawName } = await inquirer2.prompt([{
|
|
464
|
+
type: "input",
|
|
465
|
+
name: "rawName",
|
|
466
|
+
message: "Proxy name (your API will live at <name>.abz.run):",
|
|
467
|
+
transformer: (v) => normalizeName(v)
|
|
468
|
+
}]);
|
|
469
|
+
name = normalizeName(rawName);
|
|
470
|
+
if (name.length < 3) {
|
|
471
|
+
console.log(import_chalk6.default.yellow(" Name must be at least 3 characters (letters and digits only).\n"));
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const spinner2 = (0, import_ora4.default)("Checking availability...").start();
|
|
475
|
+
try {
|
|
476
|
+
const check = await checkProxyName(name, teamId, opts.apiversion);
|
|
477
|
+
spinner2.stop();
|
|
478
|
+
if (!check.canUseProjectName || !check.canUseApiVersion) {
|
|
479
|
+
console.log(import_chalk6.default.yellow(` "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}. Try another.
|
|
480
|
+
`));
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
spinner2.stop();
|
|
485
|
+
console.log(import_chalk6.default.dim(" (could not verify availability; continuing)"));
|
|
486
|
+
}
|
|
487
|
+
console.log(`${import_chalk6.default.cyan("\u2192")} Your API will live at ${import_chalk6.default.bold(`https://${name}.abz.run`)}
|
|
488
|
+
`);
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
fail("--name is required in non-interactive mode.");
|
|
493
|
+
}
|
|
494
|
+
let targetUrl = "";
|
|
495
|
+
if (opts.target !== void 0) {
|
|
496
|
+
if (!isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
|
|
497
|
+
targetUrl = opts.target.trim();
|
|
498
|
+
} else if (interactive) {
|
|
499
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
500
|
+
for (; ; ) {
|
|
501
|
+
const { url } = await inquirer2.prompt([{
|
|
502
|
+
type: "input",
|
|
503
|
+
name: "url",
|
|
504
|
+
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
505
|
+
}]);
|
|
506
|
+
if (!isHttpUrl(url)) {
|
|
507
|
+
console.log(import_chalk6.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
targetUrl = url.trim();
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
fail("--target is required in non-interactive mode.");
|
|
515
|
+
}
|
|
516
|
+
if (interactive && !opts.yes) {
|
|
517
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
518
|
+
console.log(`${import_chalk6.default.cyan("\u2192")} Auth: ${import_chalk6.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
|
|
519
|
+
const { ok } = await inquirer2.prompt([{
|
|
520
|
+
type: "confirm",
|
|
521
|
+
name: "ok",
|
|
522
|
+
message: `Create proxy "${name}" \u2192 ${targetUrl}?`,
|
|
523
|
+
default: true
|
|
524
|
+
}]);
|
|
525
|
+
if (!ok) {
|
|
526
|
+
console.log(import_chalk6.default.yellow("Cancelled."));
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const spinner = !opts.json ? (0, import_ora4.default)("Creating proxy (tenant, keys, dev portal)...").start() : null;
|
|
531
|
+
let result;
|
|
532
|
+
try {
|
|
533
|
+
result = await createProxy({ name, target_url: targetUrl, auth_type: auth, team_id: teamId, ...opts.apiversion ? { api_version: opts.apiversion } : {} });
|
|
534
|
+
spinner?.succeed(import_chalk6.default.green("Proxy created!"));
|
|
535
|
+
} catch (err) {
|
|
536
|
+
spinner?.fail("Failed to create proxy.");
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
const version2 = result.api_version || "1.0.0";
|
|
540
|
+
const keys = result.api_keys ?? {};
|
|
541
|
+
const adminKey = keys.dev ?? Object.values(keys)[0];
|
|
542
|
+
const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
|
|
543
|
+
const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
|
|
544
|
+
if (opts.json) {
|
|
545
|
+
process.stdout.write(JSON.stringify({
|
|
546
|
+
project_id: result.project_id,
|
|
547
|
+
api_version: version2,
|
|
548
|
+
proxy_url: proxyUrl,
|
|
549
|
+
dev_portal: devPortal,
|
|
550
|
+
api_key: adminKey,
|
|
551
|
+
api_keys: keys,
|
|
552
|
+
team_id: teamId
|
|
553
|
+
}) + "\n");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
console.log();
|
|
557
|
+
console.log(` ${import_chalk6.default.dim("Proxy URL: ")} ${import_chalk6.default.bold(proxyUrl)}`);
|
|
558
|
+
if (devPortal) console.log(` ${import_chalk6.default.dim("Dev portal:")} ${import_chalk6.default.bold(devPortal)}`);
|
|
559
|
+
if (adminKey) {
|
|
560
|
+
console.log();
|
|
561
|
+
console.log(` ${import_chalk6.default.dim("Consumer admin API key (dev):")}`);
|
|
562
|
+
console.log(` ${import_chalk6.default.bold.green(adminKey)}`);
|
|
563
|
+
console.log(import_chalk6.default.dim("\n Save this now \u2014 send it as the X-API-Key header. It may not be shown again."));
|
|
564
|
+
const otherEnvs = Object.keys(keys).filter((e) => e !== "dev");
|
|
565
|
+
if (otherEnvs.length) {
|
|
566
|
+
console.log(import_chalk6.default.dim(` (Separate keys were also created for: ${otherEnvs.join(", ")}.)`));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
printCurlExample(proxyUrl, auth, adminKey, devPortal);
|
|
570
|
+
console.log();
|
|
571
|
+
}
|
|
572
|
+
async function runAnonymousCreate(opts) {
|
|
573
|
+
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
574
|
+
if (!opts.json) {
|
|
575
|
+
console.log(import_chalk6.default.bold("\nCreate an API proxy"));
|
|
576
|
+
console.log(import_chalk6.default.dim("Not logged in \u2014 creating an anonymous proxy. You can claim it to your account within 30 days.\n"));
|
|
577
|
+
}
|
|
578
|
+
let body = {};
|
|
579
|
+
if (opts.config) {
|
|
580
|
+
let raw = "";
|
|
581
|
+
try {
|
|
582
|
+
raw = import_fs2.default.readFileSync(opts.config, "utf8");
|
|
583
|
+
} catch {
|
|
584
|
+
fail(`Cannot read --config file: ${opts.config}`);
|
|
585
|
+
}
|
|
586
|
+
let parsed;
|
|
587
|
+
try {
|
|
588
|
+
parsed = JSON.parse(raw);
|
|
589
|
+
} catch {
|
|
590
|
+
fail(`--config is not valid JSON: ${opts.config}`);
|
|
591
|
+
}
|
|
592
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) fail("--config must be a JSON object.");
|
|
593
|
+
body = parsed;
|
|
594
|
+
}
|
|
595
|
+
let name = opts.name !== void 0 ? normalizeName(opts.name) : typeof body.name === "string" ? body.name : void 0;
|
|
596
|
+
if (opts.name !== void 0 && name.length < 3) {
|
|
597
|
+
fail("Proxy name must be at least 3 characters (letters and digits only).");
|
|
598
|
+
}
|
|
599
|
+
if (opts.target && !isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
|
|
600
|
+
let target = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
|
|
601
|
+
const hasOtherSource = !!(body.openapi || body.github);
|
|
602
|
+
if (!target && !hasOtherSource) {
|
|
603
|
+
if (interactive) {
|
|
604
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
605
|
+
if (name === void 0) {
|
|
606
|
+
const { rawName } = await inquirer2.prompt([{
|
|
607
|
+
type: "input",
|
|
608
|
+
name: "rawName",
|
|
609
|
+
message: "Proxy name (leave blank to auto-generate):",
|
|
610
|
+
transformer: (v) => normalizeName(v)
|
|
611
|
+
}]);
|
|
612
|
+
const n = normalizeName(rawName);
|
|
613
|
+
name = n.length >= 3 ? n : void 0;
|
|
614
|
+
}
|
|
615
|
+
for (; ; ) {
|
|
616
|
+
const { url } = await inquirer2.prompt([{
|
|
617
|
+
type: "input",
|
|
618
|
+
name: "url",
|
|
619
|
+
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
620
|
+
}]);
|
|
621
|
+
if (!isHttpUrl(url)) {
|
|
622
|
+
console.log(import_chalk6.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
target = url.trim();
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
fail("A source is required: pass --target, or target/openapi/github in --config.");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (target) {
|
|
633
|
+
body.target = target;
|
|
634
|
+
body.target_url = target;
|
|
635
|
+
}
|
|
636
|
+
if (name) {
|
|
637
|
+
body.name = name;
|
|
638
|
+
body.subdomain = name;
|
|
639
|
+
}
|
|
640
|
+
if (opts.subdomain) body.subdomain = normalizeName(opts.subdomain);
|
|
641
|
+
if (opts.tenant) body.tenant = normalizeName(opts.tenant);
|
|
642
|
+
if (opts.product) body.product_slug = normalizeName(opts.product);
|
|
643
|
+
if (opts.displayName) body.display_name = opts.displayName;
|
|
644
|
+
if (opts.apiversion) body.api_version = opts.apiversion;
|
|
645
|
+
if (opts.auth && opts.auth !== "api_key") body.auth_type = opts.auth;
|
|
646
|
+
const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
647
|
+
if (opts.newSession) clearAnonCred2();
|
|
648
|
+
const cred = loadAnonCred2();
|
|
649
|
+
const spinner = !opts.json ? (0, import_ora4.default)(cred ? "Creating proxy (in your anonymous workspace)..." : "Creating proxy...").start() : null;
|
|
650
|
+
let result;
|
|
651
|
+
try {
|
|
652
|
+
if (cred) {
|
|
653
|
+
result = await cpFetch2(cred.cp_key, "/projects", { method: "POST", body: JSON.stringify(body) });
|
|
654
|
+
if (Array.isArray(result.endpoints)) {
|
|
655
|
+
result.endpoints = result.endpoints.map((e) => e.replace(/:\/\/[^/]+/, `://${result.project_id}.tryabz.run`));
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
result = await createProxyAnonymous(body);
|
|
659
|
+
if (result.cp_key && result.team_id) {
|
|
660
|
+
saveAnonCred2(result.cp_key, result.team_id, result.claim_code);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
spinner?.succeed(import_chalk6.default.green("Proxy created!"));
|
|
664
|
+
} catch (err) {
|
|
665
|
+
spinner?.fail("Failed to create proxy.");
|
|
666
|
+
throw err;
|
|
667
|
+
}
|
|
668
|
+
const version2 = result.api_version || "1.0.0";
|
|
669
|
+
const keys = result.api_keys ?? {};
|
|
670
|
+
const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
|
|
671
|
+
const prodEndpoint = (result.endpoints || []).find((e) => e.endsWith("/prod")) || (result.endpoints || [])[0];
|
|
672
|
+
if (opts.json) {
|
|
673
|
+
process.stdout.write(JSON.stringify({
|
|
674
|
+
project_id: result.project_id,
|
|
675
|
+
api_version: version2,
|
|
676
|
+
endpoints: result.endpoints,
|
|
677
|
+
api_key: apiKey,
|
|
678
|
+
api_keys: keys,
|
|
679
|
+
claim_url: result.claim_url,
|
|
680
|
+
anonymous: true
|
|
681
|
+
}) + "\n");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
console.log();
|
|
685
|
+
if (prodEndpoint) console.log(` ${import_chalk6.default.dim("Proxy URL: ")} ${import_chalk6.default.bold(prodEndpoint)}`);
|
|
686
|
+
if (result.portal) console.log(` ${import_chalk6.default.dim("Dev portal:")} ${import_chalk6.default.bold(result.portal)}`);
|
|
687
|
+
if (apiKey) {
|
|
688
|
+
console.log();
|
|
689
|
+
console.log(` ${import_chalk6.default.dim("API key:")}`);
|
|
690
|
+
console.log(` ${import_chalk6.default.bold.green(apiKey)}`);
|
|
691
|
+
console.log(import_chalk6.default.dim("\n Save this now \u2014 send it as the X-API-Key header. It may not be shown again."));
|
|
692
|
+
}
|
|
693
|
+
if (prodEndpoint) printCurlExample(prodEndpoint, opts.auth || "api_key", apiKey, result.portal);
|
|
694
|
+
const claimCode = result.claim_code || cred?.claim_code;
|
|
695
|
+
if (claimCode) {
|
|
696
|
+
console.log();
|
|
697
|
+
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous \u2014 claim within 30 days or it expires.")} Everything you create`);
|
|
698
|
+
console.log(` with the CP key shares ONE workspace. To keep it all in one shot:`);
|
|
699
|
+
console.log(` ${import_chalk6.default.cyan("apiblaze login")} ${import_chalk6.default.dim("(prompts to claim your workspace into your account)")}`);
|
|
700
|
+
console.log(import_chalk6.default.dim(` From another machine: apiblaze claim ${claimCode} (add --team <name> to merge into an existing team)`));
|
|
701
|
+
} else if (result.claim_url) {
|
|
702
|
+
console.log();
|
|
703
|
+
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous proxy \u2014 claim it to your account within 30 days or it expires:")}`);
|
|
704
|
+
console.log(` ${import_chalk6.default.bold(result.claim_url)}`);
|
|
705
|
+
}
|
|
706
|
+
console.log();
|
|
707
|
+
}
|
|
708
|
+
var import_fs2, import_chalk6, import_ora4, VALID_AUTH;
|
|
709
|
+
var init_create = __esm({
|
|
710
|
+
"src/commands/create.ts"() {
|
|
711
|
+
"use strict";
|
|
712
|
+
import_fs2 = __toESM(require("fs"));
|
|
713
|
+
import_chalk6 = __toESM(require("chalk"));
|
|
714
|
+
import_ora4 = __toESM(require("ora"));
|
|
715
|
+
init_auth();
|
|
716
|
+
init_api();
|
|
717
|
+
VALID_AUTH = ["api_key", "none", "oauth"];
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// src/lib/trace.ts
|
|
722
|
+
function setVerbose(v) {
|
|
723
|
+
verbose = v;
|
|
724
|
+
}
|
|
725
|
+
function recordCall(e) {
|
|
726
|
+
if (verbose) entries.push(e);
|
|
727
|
+
}
|
|
728
|
+
function maskBody(body) {
|
|
729
|
+
if (body === void 0) return void 0;
|
|
730
|
+
return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
|
|
731
|
+
}
|
|
732
|
+
function renderTrace() {
|
|
733
|
+
if (!verbose || entries.length === 0) return;
|
|
734
|
+
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
735
|
+
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
736
|
+
console.log(
|
|
737
|
+
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
|
|
738
|
+
);
|
|
739
|
+
entries.forEach((e, i) => {
|
|
740
|
+
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
741
|
+
if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
|
|
742
|
+
const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
|
|
743
|
+
const masked = maskBody(e.body);
|
|
744
|
+
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
745
|
+
console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
|
|
746
|
+
console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
|
|
747
|
+
if (hasBody) {
|
|
748
|
+
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
749
|
+
console.log(import_chalk15.default.green(` -d '${masked}'`));
|
|
750
|
+
}
|
|
751
|
+
if (i < entries.length - 1) console.log();
|
|
752
|
+
});
|
|
753
|
+
entries.length = 0;
|
|
754
|
+
}
|
|
755
|
+
var import_chalk15, CONTROL_API_VERSION, verbose, entries, SECRET_KEY;
|
|
756
|
+
var init_trace = __esm({
|
|
757
|
+
"src/lib/trace.ts"() {
|
|
758
|
+
"use strict";
|
|
759
|
+
import_chalk15 = __toESM(require("chalk"));
|
|
760
|
+
CONTROL_API_VERSION = "1.0.0";
|
|
761
|
+
verbose = false;
|
|
762
|
+
entries = [];
|
|
763
|
+
SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// src/lib/admin.ts
|
|
768
|
+
async function admin(call) {
|
|
769
|
+
const token = getAccessToken();
|
|
770
|
+
const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
|
|
469
771
|
method: "POST",
|
|
470
|
-
|
|
471
|
-
body: {
|
|
472
|
-
summary: `Create tenant "${display.trim()}"`
|
|
772
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
773
|
+
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
473
774
|
});
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
775
|
+
let data = null;
|
|
776
|
+
try {
|
|
777
|
+
data = await res.json();
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
781
|
+
maybePrintBilling(data);
|
|
782
|
+
if (!res.ok) {
|
|
783
|
+
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
784
|
+
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
785
|
+
}
|
|
786
|
+
return data;
|
|
787
|
+
}
|
|
788
|
+
function maybePrintBilling(data) {
|
|
789
|
+
const b = data?.billing;
|
|
790
|
+
if (b && typeof b.charged_cents === "number") {
|
|
791
|
+
const usd = (b.charged_cents / 100).toFixed(2);
|
|
792
|
+
const rem = typeof b.credits_remaining === "number" ? ` \xB7 $${(b.credits_remaining / 100).toFixed(2)} credit left` : "";
|
|
793
|
+
console.log(import_chalk16.default.magenta(` \u{1F4B3} Charged $${usd}${rem}`));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
var import_chalk16, DASHBOARD_BASE3;
|
|
797
|
+
var init_admin = __esm({
|
|
798
|
+
"src/lib/admin.ts"() {
|
|
799
|
+
"use strict";
|
|
800
|
+
import_chalk16 = __toESM(require("chalk"));
|
|
801
|
+
init_auth();
|
|
802
|
+
init_trace();
|
|
803
|
+
init_types();
|
|
804
|
+
DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
// src/lib/tenant-pick.ts
|
|
809
|
+
var tenant_pick_exports = {};
|
|
810
|
+
__export(tenant_pick_exports, {
|
|
811
|
+
pickTenant: () => pickTenant
|
|
812
|
+
});
|
|
813
|
+
async function fetchPage(teamId, q) {
|
|
814
|
+
const out = await admin({
|
|
815
|
+
method: "GET",
|
|
816
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1&limit=${PAGE}${q ? `&q=${encodeURIComponent(q)}` : ""}`,
|
|
817
|
+
summary: q ? `Search tenants matching "${q}"` : "List tenants (first page)"
|
|
818
|
+
});
|
|
819
|
+
const rows = (out?.tenants ?? []).map(
|
|
820
|
+
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
821
|
+
);
|
|
822
|
+
return { rows, total: out?.total ?? rows.length, defaultTenant: out?.default_tenant ?? null };
|
|
823
|
+
}
|
|
824
|
+
function label(t, defaultTenant, active) {
|
|
825
|
+
const tags = [
|
|
826
|
+
t.tenant_name === active ? import_chalk22.default.cyan("active scope") : "",
|
|
827
|
+
t.tenant_name === defaultTenant ? import_chalk22.default.dim("team default") : ""
|
|
828
|
+
].filter(Boolean).join(", ");
|
|
829
|
+
const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk22.default.dim(` ${t.display_name}`) : "";
|
|
830
|
+
return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
|
|
831
|
+
}
|
|
832
|
+
async function pickTenant(teamId, opts = {}) {
|
|
833
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
834
|
+
const active = loadCredentials()?.activeTenant;
|
|
835
|
+
let q = opts.initialQuery ?? "";
|
|
836
|
+
for (; ; ) {
|
|
837
|
+
const spinner = (0, import_ora8.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
|
|
838
|
+
const page = await fetchPage(teamId, q).finally(() => spinner.stop());
|
|
839
|
+
if (!page.total && !q) {
|
|
840
|
+
if (opts.allowCreate) {
|
|
841
|
+
const { make } = await inquirer2.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
|
|
842
|
+
if (make) return await createTenantInline(teamId);
|
|
843
|
+
}
|
|
844
|
+
console.error(import_chalk22.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
const truncated = page.total > page.rows.length;
|
|
848
|
+
const choices = page.rows.map((t) => ({
|
|
849
|
+
name: label(t, page.defaultTenant, active),
|
|
850
|
+
value: t.tenant_name
|
|
851
|
+
}));
|
|
852
|
+
if (truncated || q) {
|
|
853
|
+
choices.push(new inquirer2.Separator(import_chalk22.default.dim(
|
|
854
|
+
truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
|
|
855
|
+
)));
|
|
856
|
+
choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
|
|
857
|
+
}
|
|
858
|
+
if (q) choices.push({ name: "\u21BA Show all (clear search)", value: "\0clear" });
|
|
859
|
+
if (opts.allowCreate) choices.push({ name: "\uFF0B Create a new tenant\u2026", value: "\0create" });
|
|
860
|
+
if (opts.allowBack) choices.push({ name: "\u2190 Back", value: "\0back" });
|
|
861
|
+
const { picked } = await inquirer2.prompt([{
|
|
862
|
+
type: "list",
|
|
863
|
+
name: "picked",
|
|
864
|
+
pageSize: PAGE + 5,
|
|
865
|
+
message: opts.message ?? "Which tenant?",
|
|
866
|
+
default: active && page.rows.some((t) => t.tenant_name === active) ? active : void 0,
|
|
867
|
+
choices
|
|
868
|
+
}]);
|
|
869
|
+
if (picked === "\0back") return null;
|
|
870
|
+
if (picked === "\0clear") {
|
|
871
|
+
q = "";
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (picked === "\0create") return await createTenantInline(teamId);
|
|
875
|
+
if (picked === "\0search") {
|
|
876
|
+
const { nq } = await inquirer2.prompt([{ type: "input", name: "nq", message: "Search (name or display name):", default: q }]);
|
|
877
|
+
q = String(nq ?? "").trim();
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
return picked;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
async function createTenantInline(teamId) {
|
|
884
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
885
|
+
for (; ; ) {
|
|
886
|
+
const { name } = await inquirer2.prompt([{
|
|
887
|
+
type: "input",
|
|
888
|
+
name: "name",
|
|
889
|
+
message: `Tenant name ${import_chalk22.default.dim("(lowercase letters/numbers; globally unique \u2014 becomes {name}.portal.apiblaze.com)")}:`,
|
|
890
|
+
validate: (s) => /^[a-z0-9]+$/.test(s.trim()) ? true : "lowercase letters and numbers only"
|
|
891
|
+
}]);
|
|
892
|
+
const slugInput = name.trim();
|
|
893
|
+
try {
|
|
894
|
+
const out = await admin({
|
|
895
|
+
method: "POST",
|
|
896
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants`,
|
|
897
|
+
body: { tenant_name: slugInput, display_name: slugInput },
|
|
898
|
+
summary: `Create tenant "${slugInput}"`
|
|
899
|
+
});
|
|
900
|
+
const slug = out?.tenant_name ?? out?.tenant?.tenant_name ?? slugInput;
|
|
901
|
+
console.log(import_chalk22.default.green(` Tenant ${import_chalk22.default.bold(slug)} created.`));
|
|
902
|
+
return slug;
|
|
903
|
+
} catch (err) {
|
|
904
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
905
|
+
if (/taken|unique|exists|reserved|conflict/i.test(msg)) {
|
|
906
|
+
console.log(import_chalk22.default.yellow(` "${slugInput}" is not available (tenant names are global): ${msg}`));
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
throw err;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
477
912
|
}
|
|
478
913
|
var import_chalk22, import_ora8, PAGE;
|
|
479
914
|
var init_tenant_pick = __esm({
|
|
@@ -492,7 +927,7 @@ var import_commander = require("commander");
|
|
|
492
927
|
var import_chalk34 = __toESM(require("chalk"));
|
|
493
928
|
|
|
494
929
|
// package.json
|
|
495
|
-
var version = "0.
|
|
930
|
+
var version = "0.15.0";
|
|
496
931
|
|
|
497
932
|
// src/index.ts
|
|
498
933
|
init_types();
|
|
@@ -501,43 +936,7 @@ init_types();
|
|
|
501
936
|
var import_chalk2 = __toESM(require("chalk"));
|
|
502
937
|
var import_ora = __toESM(require("ora"));
|
|
503
938
|
init_auth();
|
|
504
|
-
|
|
505
|
-
// src/lib/team.ts
|
|
506
|
-
var import_chalk = __toESM(require("chalk"));
|
|
507
|
-
init_api();
|
|
508
|
-
async function resolveLinkedTeam(opts) {
|
|
509
|
-
let teams;
|
|
510
|
-
try {
|
|
511
|
-
teams = await getTeams();
|
|
512
|
-
} catch {
|
|
513
|
-
return opts.preferredId ? { teamId: opts.preferredId } : null;
|
|
514
|
-
}
|
|
515
|
-
if (teams.length === 0) return null;
|
|
516
|
-
const preferred = opts.preferredId ? teams.find((t) => t.teamId === opts.preferredId) : void 0;
|
|
517
|
-
if (preferred) return { teamId: preferred.teamId, teamName: preferred.name };
|
|
518
|
-
if (opts.preferredId) {
|
|
519
|
-
console.log(import_chalk.default.yellow("\nYour previously linked team is no longer available."));
|
|
520
|
-
}
|
|
521
|
-
if (teams.length === 1) {
|
|
522
|
-
console.log(`${import_chalk.default.cyan("\u2192")} Linking to your team ${import_chalk.default.bold(teams[0].name)}.`);
|
|
523
|
-
return { teamId: teams[0].teamId, teamName: teams[0].name };
|
|
524
|
-
}
|
|
525
|
-
if (!opts.interactive) {
|
|
526
|
-
console.log(import_chalk.default.yellow(`Linking to "${teams[0].name}" \u2014 pass --team to choose another.`));
|
|
527
|
-
return { teamId: teams[0].teamId, teamName: teams[0].name };
|
|
528
|
-
}
|
|
529
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
530
|
-
const { chosen } = await inquirer2.prompt([{
|
|
531
|
-
type: "list",
|
|
532
|
-
name: "chosen",
|
|
533
|
-
message: "Which team do you want to link to?",
|
|
534
|
-
choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
|
|
535
|
-
}]);
|
|
536
|
-
const picked = teams.find((t) => t.teamId === chosen);
|
|
537
|
-
return { teamId: picked.teamId, teamName: picked.name };
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// src/commands/login.ts
|
|
939
|
+
init_team();
|
|
541
940
|
var DASHBOARD_BASE2 = "https://dashboard.apiblaze.com";
|
|
542
941
|
function openBrowser(url) {
|
|
543
942
|
const { exec } = require("child_process");
|
|
@@ -613,7 +1012,12 @@ async function runLogin() {
|
|
|
613
1012
|
}
|
|
614
1013
|
let teamId = defaultTeamId ?? void 0;
|
|
615
1014
|
let teamName;
|
|
616
|
-
const linked = await resolveLinkedTeam({
|
|
1015
|
+
const linked = await resolveLinkedTeam({
|
|
1016
|
+
preferredId: defaultTeamId ?? void 0,
|
|
1017
|
+
interactive: !!process.stdin.isTTY,
|
|
1018
|
+
// >1 team → ASK (JWT default is just the cursor). One team → link silently.
|
|
1019
|
+
promptWhenMultiple: true
|
|
1020
|
+
});
|
|
617
1021
|
if (linked) {
|
|
618
1022
|
teamId = linked.teamId;
|
|
619
1023
|
teamName = linked.teamName;
|
|
@@ -838,6 +1242,9 @@ function formatCapturedRequest(req, note) {
|
|
|
838
1242
|
].join("\n");
|
|
839
1243
|
}
|
|
840
1244
|
|
|
1245
|
+
// src/commands/dev.ts
|
|
1246
|
+
init_team();
|
|
1247
|
+
|
|
841
1248
|
// src/lib/random-name.ts
|
|
842
1249
|
var ADJECTIVES = [
|
|
843
1250
|
"amber",
|
|
@@ -1178,587 +1585,253 @@ async function offerAutoCreate(teamId, port) {
|
|
|
1178
1585
|
const targets = await getLocalhostTargets(teamId).catch(() => []);
|
|
1179
1586
|
const created = targets.find((t) => t.projectId === result.project_id);
|
|
1180
1587
|
if (!created) {
|
|
1181
|
-
console.log(import_chalk4.default.yellow(" Proxy created, but it did not appear as a localhost target \u2014 try `apiblaze dev` again."));
|
|
1182
|
-
return null;
|
|
1183
|
-
}
|
|
1184
|
-
return created;
|
|
1185
|
-
}
|
|
1186
|
-
function isInternalTarget(url) {
|
|
1187
|
-
if (!url) return false;
|
|
1188
|
-
try {
|
|
1189
|
-
const h = new URL(url).hostname.toLowerCase();
|
|
1190
|
-
return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h === "0.0.0.0" || /^127\./.test(h) || /^10\./.test(h) || /^192\.168\./.test(h) || /^169\.254\./.test(h) || /^172\.(1[6-9]|2\d|3[01])\./.test(h);
|
|
1191
|
-
} catch {
|
|
1192
|
-
return false;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
function printTunnelEndpoints(restore, targets) {
|
|
1196
|
-
if (restore.length === 0) return;
|
|
1197
|
-
console.log(import_chalk4.default.bold("\nYour proxy is live at:"));
|
|
1198
|
-
for (const r of restore) {
|
|
1199
|
-
const label2 = targets.find((t) => t.projectId === r.projectId)?.projectName ?? r.projectId;
|
|
1200
|
-
console.log(`
|
|
1201
|
-
${import_chalk4.default.bold(label2)}`);
|
|
1202
|
-
const internalEnvs = Object.keys(r.environments ?? {}).filter((e) => isInternalTarget(r.environments[e]?.target));
|
|
1203
|
-
const envs = internalEnvs.includes("dev") ? ["dev"] : internalEnvs.length ? internalEnvs : ["dev"];
|
|
1204
|
-
for (const env of envs) {
|
|
1205
|
-
console.log(` ${import_chalk4.default.dim("API: ")} ${import_chalk4.default.cyan(`https://${r.projectId}.abz.run/${r.apiVersion}/${env}/`)}`);
|
|
1206
|
-
}
|
|
1207
|
-
if (r.tenant) {
|
|
1208
|
-
console.log(` ${import_chalk4.default.dim("Portal:")} ${import_chalk4.default.cyan(`https://${r.tenant}.portal.apiblaze.com/${r.apiVersion}`)}`);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
async function probeLocalServer(port) {
|
|
1213
|
-
const controller = new AbortController();
|
|
1214
|
-
const timer = setTimeout(() => controller.abort(), 1500);
|
|
1215
|
-
try {
|
|
1216
|
-
await fetch(`http://127.0.0.1:${port}/`, { method: "HEAD", signal: controller.signal });
|
|
1217
|
-
return true;
|
|
1218
|
-
} catch (err) {
|
|
1219
|
-
if (err?.name === "AbortError") return true;
|
|
1220
|
-
const code = err?.cause?.code;
|
|
1221
|
-
return !(code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EHOSTUNREACH");
|
|
1222
|
-
} finally {
|
|
1223
|
-
clearTimeout(timer);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
async function runDev(options) {
|
|
1227
|
-
const creds = await ensureLoggedIn(!!process.stdin.isTTY);
|
|
1228
|
-
const linked = await resolveLinkedTeam({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
1229
|
-
if (!linked) {
|
|
1230
|
-
console.error(import_chalk4.default.red("No team available. Run `apiblaze login` to set up your team."));
|
|
1231
|
-
process.exit(1);
|
|
1232
|
-
}
|
|
1233
|
-
const teamId = linked.teamId;
|
|
1234
|
-
if (linked.teamId !== creds.teamId || linked.teamName !== creds.teamName) {
|
|
1235
|
-
saveCredentials({ ...creds, teamId: linked.teamId, teamName: linked.teamName });
|
|
1236
|
-
}
|
|
1237
|
-
if (linked.teamName) {
|
|
1238
|
-
console.log(`${import_chalk4.default.cyan("\u2192")} Team: ${import_chalk4.default.bold(linked.teamName)}`);
|
|
1239
|
-
}
|
|
1240
|
-
let targets;
|
|
1241
|
-
{
|
|
1242
|
-
const spinner = (0, import_ora2.default)("Fetching your localhost projects...").start();
|
|
1243
|
-
try {
|
|
1244
|
-
targets = await getLocalhostTargets(teamId);
|
|
1245
|
-
spinner.stop();
|
|
1246
|
-
} catch (err) {
|
|
1247
|
-
spinner.fail("Failed to fetch projects.");
|
|
1248
|
-
throw err;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
let selectedTargets;
|
|
1252
|
-
if (targets.length === 0) {
|
|
1253
|
-
const created = await offerAutoCreate(teamId, options.port);
|
|
1254
|
-
if (!created) {
|
|
1255
|
-
console.log("Set a project's upstream target to localhost or a private IP, then try again.");
|
|
1256
|
-
process.exit(0);
|
|
1257
|
-
}
|
|
1258
|
-
selectedTargets = [created];
|
|
1259
|
-
} else if (targets.length === 1) {
|
|
1260
|
-
const { confirmed } = await import_inquirer.default.prompt([{
|
|
1261
|
-
type: "confirm",
|
|
1262
|
-
name: "confirmed",
|
|
1263
|
-
message: `Found 1 project with an internal target \u2014 tunnel "${import_chalk4.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
|
|
1264
|
-
default: true
|
|
1265
|
-
}]);
|
|
1266
|
-
if (!confirmed) {
|
|
1267
|
-
console.log("Aborted.");
|
|
1268
|
-
process.exit(0);
|
|
1269
|
-
}
|
|
1270
|
-
selectedTargets = targets;
|
|
1271
|
-
} else {
|
|
1272
|
-
const ALL = "__all__";
|
|
1273
|
-
const { chosen } = await import_inquirer.default.prompt([{
|
|
1274
|
-
type: "list",
|
|
1275
|
-
name: "chosen",
|
|
1276
|
-
message: `Found ${targets.length} projects with an internal target \u2014 pick one to tunnel:`,
|
|
1277
|
-
choices: [
|
|
1278
|
-
...targets.map((t) => ({
|
|
1279
|
-
name: `${import_chalk4.default.bold(t.projectName)} (${t.tenantName}) \u2014 ${t.target}`,
|
|
1280
|
-
value: t
|
|
1281
|
-
})),
|
|
1282
|
-
new import_inquirer.default.Separator(),
|
|
1283
|
-
{ name: `Tunnel all ${targets.length}`, value: ALL }
|
|
1284
|
-
]
|
|
1285
|
-
}]);
|
|
1286
|
-
selectedTargets = chosen === ALL ? targets : [chosen];
|
|
1287
|
-
}
|
|
1288
|
-
console.log(
|
|
1289
|
-
import_chalk4.default.green(`
|
|
1290
|
-
Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
|
|
1291
|
-
`)
|
|
1292
|
-
);
|
|
1293
|
-
let recordSink;
|
|
1294
|
-
let captureStream;
|
|
1295
|
-
if (options.captureFile) {
|
|
1296
|
-
captureStream = import_fs.default.createWriteStream(options.captureFile, { flags: "a" });
|
|
1297
|
-
recordSink = (r) => captureStream.write(JSON.stringify(r) + "\n");
|
|
1298
|
-
console.log(import_chalk4.default.gray(`Streaming full traffic to ${options.captureFile}
|
|
1299
|
-
`));
|
|
1300
|
-
}
|
|
1301
|
-
let restore = [];
|
|
1302
|
-
let connect;
|
|
1303
|
-
{
|
|
1304
|
-
const spinner = (0, import_ora2.default)("Registering tunnel with APIblaze...").start();
|
|
1305
|
-
try {
|
|
1306
|
-
const result = await putDevTunnel({
|
|
1307
|
-
targets: selectedTargets.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId }))
|
|
1308
|
-
});
|
|
1309
|
-
restore = result.restore ?? [];
|
|
1310
|
-
connect = result.connect;
|
|
1311
|
-
spinner.succeed("Tunnel registered.");
|
|
1312
|
-
} catch (err) {
|
|
1313
|
-
spinner.fail("Failed to register tunnel.");
|
|
1314
|
-
throw err;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
printTunnelEndpoints(restore, selectedTargets);
|
|
1318
|
-
const clients = connect.projects.map(
|
|
1319
|
-
(projectId) => startTunnelClient({
|
|
1320
|
-
connectUrl: connect.url,
|
|
1321
|
-
token: connect.token,
|
|
1322
|
-
projectId,
|
|
1323
|
-
localPort: options.port,
|
|
1324
|
-
onEntry: (entry) => console.log(formatLogLine(entry)),
|
|
1325
|
-
onStatus: (status) => console.log(import_chalk4.default.gray(`[${projectId}] ${status}`)),
|
|
1326
|
-
onCapture: (req, note) => console.log(formatCapturedRequest(req, note)),
|
|
1327
|
-
onCaptureStart: () => console.log(
|
|
1328
|
-
import_chalk4.default.magenta(`
|
|
1329
|
-
\u26B2 No local server on port ${options.port} yet \u2014 capturing requests below. Start your server and they'll forward automatically.
|
|
1330
|
-
`)
|
|
1331
|
-
),
|
|
1332
|
-
onResume: () => console.log(
|
|
1333
|
-
import_chalk4.default.green(`
|
|
1334
|
-
\u2713 Local server detected on port ${options.port} \u2014 forwarding resumed.
|
|
1335
|
-
`)
|
|
1336
|
-
),
|
|
1337
|
-
onRecord: recordSink
|
|
1338
|
-
})
|
|
1339
|
-
);
|
|
1340
|
-
const localUp = await probeLocalServer(options.port);
|
|
1341
|
-
console.log("\n" + import_chalk4.default.gray("\u2500".repeat(60)));
|
|
1342
|
-
console.log(import_chalk4.default.bold("Live traffic") + import_chalk4.default.gray(" (Ctrl+C to stop)"));
|
|
1343
|
-
console.log(
|
|
1344
|
-
localUp ? import_chalk4.default.green(`\u2713 Local server detected on port ${options.port} \u2014 forwarding live.`) : import_chalk4.default.magenta(`\u26B2 Nothing listening on port ${options.port} yet \u2014 requests will be captured until your server starts.`)
|
|
1345
|
-
);
|
|
1346
|
-
console.log(import_chalk4.default.gray("\u2500".repeat(60)) + "\n");
|
|
1347
|
-
let isCleaningUp = false;
|
|
1348
|
-
async function cleanup() {
|
|
1349
|
-
if (isCleaningUp) return;
|
|
1350
|
-
isCleaningUp = true;
|
|
1351
|
-
console.log(import_chalk4.default.gray("\n\nShutting down..."));
|
|
1352
|
-
for (const client of clients) client.close();
|
|
1353
|
-
captureStream?.end();
|
|
1354
|
-
await deleteDevTunnel(restore).catch(() => {
|
|
1355
|
-
});
|
|
1356
|
-
console.log(import_chalk4.default.green("Tunnel stopped."));
|
|
1357
|
-
process.exit(0);
|
|
1358
|
-
}
|
|
1359
|
-
process.on("SIGINT", () => void cleanup());
|
|
1360
|
-
process.on("SIGTERM", () => void cleanup());
|
|
1361
|
-
await new Promise(() => {
|
|
1362
|
-
});
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
// src/commands/projects.ts
|
|
1366
|
-
var import_chalk5 = __toESM(require("chalk"));
|
|
1367
|
-
var import_ora3 = __toESM(require("ora"));
|
|
1368
|
-
init_auth();
|
|
1369
|
-
init_api();
|
|
1370
|
-
async function runProjects() {
|
|
1371
|
-
const creds = loadCredentials();
|
|
1372
|
-
if (!creds) {
|
|
1373
|
-
console.error(import_chalk5.default.red("Not logged in. Run `apiblaze login` first."));
|
|
1374
|
-
process.exit(1);
|
|
1375
|
-
}
|
|
1376
|
-
if (creds.githubHandle) {
|
|
1377
|
-
console.log(`${import_chalk5.default.cyan("\u2192")} Logged in as ${import_chalk5.default.bold("@" + creds.githubHandle)}`);
|
|
1378
|
-
}
|
|
1379
|
-
let teamId = creds.teamId;
|
|
1380
|
-
let teamName = creds.teamName;
|
|
1381
|
-
if (!teamId) {
|
|
1382
|
-
const teams = await getTeams().catch(() => []);
|
|
1383
|
-
if (teams.length === 1) {
|
|
1384
|
-
teamId = teams[0].teamId;
|
|
1385
|
-
teamName = teams[0].name;
|
|
1386
|
-
} else if (teams.length > 1) {
|
|
1387
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
1388
|
-
const { chosen } = await inquirer2.prompt([{
|
|
1389
|
-
type: "list",
|
|
1390
|
-
name: "chosen",
|
|
1391
|
-
message: "Which team do you want to use?",
|
|
1392
|
-
choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
|
|
1393
|
-
}]);
|
|
1394
|
-
teamId = chosen;
|
|
1395
|
-
teamName = teams.find((t) => t.teamId === chosen)?.name;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
if (!teamId) {
|
|
1399
|
-
console.error(import_chalk5.default.red("No team found. Run `apiblaze login` to set up your team."));
|
|
1400
|
-
process.exit(1);
|
|
1401
|
-
}
|
|
1402
|
-
console.log(`${import_chalk5.default.cyan("\u2192")} Team: ${import_chalk5.default.bold(teamName ?? teamId)}
|
|
1403
|
-
`);
|
|
1404
|
-
const spinner = (0, import_ora3.default)("Fetching projects...").start();
|
|
1405
|
-
let projects;
|
|
1406
|
-
try {
|
|
1407
|
-
projects = await getProjects(teamId);
|
|
1408
|
-
spinner.stop();
|
|
1409
|
-
} catch (err) {
|
|
1410
|
-
spinner.fail("Failed to fetch projects.");
|
|
1411
|
-
throw err;
|
|
1412
|
-
}
|
|
1413
|
-
if (projects.length === 0) {
|
|
1414
|
-
console.log(import_chalk5.default.yellow("No projects found for this team."));
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
|
-
const width = Math.max(...projects.map((p) => p.projectName.length));
|
|
1418
|
-
for (const p of projects) {
|
|
1419
|
-
console.log(` ${import_chalk5.default.bold(p.projectName.padEnd(width))} ${import_chalk5.default.dim("v" + p.apiVersion)}`);
|
|
1420
|
-
}
|
|
1421
|
-
console.log(import_chalk5.default.dim(`
|
|
1422
|
-
${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
// src/commands/create.ts
|
|
1426
|
-
var import_fs2 = __toESM(require("fs"));
|
|
1427
|
-
var import_chalk6 = __toESM(require("chalk"));
|
|
1428
|
-
var import_ora4 = __toESM(require("ora"));
|
|
1429
|
-
init_auth();
|
|
1430
|
-
init_api();
|
|
1431
|
-
function normalizeName(raw) {
|
|
1432
|
-
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1433
|
-
}
|
|
1434
|
-
function isHttpUrl(s) {
|
|
1435
|
-
try {
|
|
1436
|
-
const u = new URL((s || "").trim());
|
|
1437
|
-
return u.protocol === "http:" || u.protocol === "https:";
|
|
1438
|
-
} catch {
|
|
1439
|
-
return false;
|
|
1588
|
+
console.log(import_chalk4.default.yellow(" Proxy created, but it did not appear as a localhost target \u2014 try `apiblaze dev` again."));
|
|
1589
|
+
return null;
|
|
1440
1590
|
}
|
|
1591
|
+
return created;
|
|
1441
1592
|
}
|
|
1442
|
-
function
|
|
1593
|
+
function isInternalTarget(url) {
|
|
1594
|
+
if (!url) return false;
|
|
1443
1595
|
try {
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
if (dot < 0) return devPortal;
|
|
1447
|
-
const product = u.hostname.slice(0, dot).split("-")[0];
|
|
1448
|
-
u.hostname = `${product}${u.hostname.slice(dot)}`;
|
|
1449
|
-
return u.toString();
|
|
1596
|
+
const h = new URL(url).hostname.toLowerCase();
|
|
1597
|
+
return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h === "0.0.0.0" || /^127\./.test(h) || /^10\./.test(h) || /^192\.168\./.test(h) || /^169\.254\./.test(h) || /^172\.(1[6-9]|2\d|3[01])\./.test(h);
|
|
1450
1598
|
} catch {
|
|
1451
|
-
return
|
|
1599
|
+
return false;
|
|
1452
1600
|
}
|
|
1453
1601
|
}
|
|
1454
|
-
function
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1602
|
+
function printTunnelEndpoints(restore, targets) {
|
|
1603
|
+
if (restore.length === 0) return;
|
|
1604
|
+
console.log(import_chalk4.default.bold("\nYour proxy is live at:"));
|
|
1605
|
+
for (const r of restore) {
|
|
1606
|
+
const label2 = targets.find((t) => t.projectId === r.projectId)?.projectName ?? r.projectId;
|
|
1607
|
+
console.log(`
|
|
1608
|
+
${import_chalk4.default.bold(label2)}`);
|
|
1609
|
+
const internalEnvs = Object.keys(r.environments ?? {}).filter((e) => isInternalTarget(r.environments[e]?.target));
|
|
1610
|
+
const envs = internalEnvs.includes("dev") ? ["dev"] : internalEnvs.length ? internalEnvs : ["dev"];
|
|
1611
|
+
for (const env of envs) {
|
|
1612
|
+
console.log(` ${import_chalk4.default.dim("API: ")} ${import_chalk4.default.cyan(`https://${r.projectId}.abz.run/${r.apiVersion}/${env}/`)}`);
|
|
1613
|
+
}
|
|
1614
|
+
if (r.tenant) {
|
|
1615
|
+
console.log(` ${import_chalk4.default.dim("Portal:")} ${import_chalk4.default.cyan(`https://${r.tenant}.portal.apiblaze.com/${r.apiVersion}`)}`);
|
|
1616
|
+
}
|
|
1462
1617
|
}
|
|
1463
|
-
if (authType === "none") return `curl ${url}`;
|
|
1464
|
-
return null;
|
|
1465
1618
|
}
|
|
1466
|
-
function
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1619
|
+
async function probeLocalServer(port) {
|
|
1620
|
+
const controller = new AbortController();
|
|
1621
|
+
const timer = setTimeout(() => controller.abort(), 1500);
|
|
1622
|
+
try {
|
|
1623
|
+
await fetch(`http://127.0.0.1:${port}/`, { method: "HEAD", signal: controller.signal });
|
|
1624
|
+
return true;
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
if (err?.name === "AbortError") return true;
|
|
1627
|
+
const code = err?.cause?.code;
|
|
1628
|
+
return !(code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EHOSTUNREACH");
|
|
1629
|
+
} finally {
|
|
1630
|
+
clearTimeout(timer);
|
|
1475
1631
|
}
|
|
1476
1632
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
const
|
|
1480
|
-
if (!
|
|
1481
|
-
|
|
1482
|
-
|
|
1633
|
+
async function runDev(options) {
|
|
1634
|
+
const creds = await ensureLoggedIn(!!process.stdin.isTTY);
|
|
1635
|
+
const linked = await resolveLinkedTeam({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
1636
|
+
if (!linked) {
|
|
1637
|
+
console.error(import_chalk4.default.red("No team available. Run `apiblaze login` to set up your team."));
|
|
1638
|
+
process.exit(1);
|
|
1483
1639
|
}
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
fail(`Invalid --auth "${auth}". Use one of: ${VALID_AUTH.join(", ")}.`);
|
|
1640
|
+
const teamId = linked.teamId;
|
|
1641
|
+
if (linked.teamId !== creds.teamId || linked.teamName !== creds.teamName) {
|
|
1642
|
+
saveCredentials({ ...creds, teamId: linked.teamId, teamName: linked.teamName });
|
|
1488
1643
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
if (opts.team.startsWith("team_")) {
|
|
1492
|
-
teamId = opts.team;
|
|
1493
|
-
} else {
|
|
1494
|
-
const teams = await getTeams().catch(() => []);
|
|
1495
|
-
const match = teams.find(
|
|
1496
|
-
(t) => t.teamId === opts.team || t.name.toLowerCase() === opts.team.toLowerCase()
|
|
1497
|
-
);
|
|
1498
|
-
if (!match) {
|
|
1499
|
-
fail(`Team "${opts.team}" not found. Run \`apiblaze team\` to see your teams.`);
|
|
1500
|
-
}
|
|
1501
|
-
teamId = match.teamId;
|
|
1502
|
-
}
|
|
1644
|
+
if (linked.teamName) {
|
|
1645
|
+
console.log(`${import_chalk4.default.cyan("\u2192")} Team: ${import_chalk4.default.bold(linked.teamName)}`);
|
|
1503
1646
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
fail(
|
|
1512
|
-
|
|
1513
|
-
} else if (interactive) {
|
|
1514
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
1515
|
-
for (; ; ) {
|
|
1516
|
-
const { rawName } = await inquirer2.prompt([{
|
|
1517
|
-
type: "input",
|
|
1518
|
-
name: "rawName",
|
|
1519
|
-
message: "Proxy name (your API will live at <name>.abz.run):",
|
|
1520
|
-
transformer: (v) => normalizeName(v)
|
|
1521
|
-
}]);
|
|
1522
|
-
name = normalizeName(rawName);
|
|
1523
|
-
if (name.length < 3) {
|
|
1524
|
-
console.log(import_chalk6.default.yellow(" Name must be at least 3 characters (letters and digits only).\n"));
|
|
1525
|
-
continue;
|
|
1526
|
-
}
|
|
1527
|
-
const spinner2 = (0, import_ora4.default)("Checking availability...").start();
|
|
1528
|
-
try {
|
|
1529
|
-
const check = await checkProxyName(name, teamId, opts.apiversion);
|
|
1530
|
-
spinner2.stop();
|
|
1531
|
-
if (!check.canUseProjectName || !check.canUseApiVersion) {
|
|
1532
|
-
console.log(import_chalk6.default.yellow(` "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}. Try another.
|
|
1533
|
-
`));
|
|
1534
|
-
continue;
|
|
1535
|
-
}
|
|
1536
|
-
} catch {
|
|
1537
|
-
spinner2.stop();
|
|
1538
|
-
console.log(import_chalk6.default.dim(" (could not verify availability; continuing)"));
|
|
1539
|
-
}
|
|
1540
|
-
console.log(`${import_chalk6.default.cyan("\u2192")} Your API will live at ${import_chalk6.default.bold(`https://${name}.abz.run`)}
|
|
1541
|
-
`);
|
|
1542
|
-
break;
|
|
1647
|
+
let targets;
|
|
1648
|
+
{
|
|
1649
|
+
const spinner = (0, import_ora2.default)("Fetching your localhost projects...").start();
|
|
1650
|
+
try {
|
|
1651
|
+
targets = await getLocalhostTargets(teamId);
|
|
1652
|
+
spinner.stop();
|
|
1653
|
+
} catch (err) {
|
|
1654
|
+
spinner.fail("Failed to fetch projects.");
|
|
1655
|
+
throw err;
|
|
1543
1656
|
}
|
|
1544
|
-
} else {
|
|
1545
|
-
fail("--name is required in non-interactive mode.");
|
|
1546
1657
|
}
|
|
1547
|
-
let
|
|
1548
|
-
if (
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
for (; ; ) {
|
|
1554
|
-
const { url } = await inquirer2.prompt([{
|
|
1555
|
-
type: "input",
|
|
1556
|
-
name: "url",
|
|
1557
|
-
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
1558
|
-
}]);
|
|
1559
|
-
if (!isHttpUrl(url)) {
|
|
1560
|
-
console.log(import_chalk6.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
1561
|
-
continue;
|
|
1562
|
-
}
|
|
1563
|
-
targetUrl = url.trim();
|
|
1564
|
-
break;
|
|
1658
|
+
let selectedTargets;
|
|
1659
|
+
if (targets.length === 0) {
|
|
1660
|
+
const created = await offerAutoCreate(teamId, options.port);
|
|
1661
|
+
if (!created) {
|
|
1662
|
+
console.log("Set a project's upstream target to localhost or a private IP, then try again.");
|
|
1663
|
+
process.exit(0);
|
|
1565
1664
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
if (interactive && !opts.yes) {
|
|
1570
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
1571
|
-
console.log(`${import_chalk6.default.cyan("\u2192")} Auth: ${import_chalk6.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
|
|
1572
|
-
const { ok } = await inquirer2.prompt([{
|
|
1665
|
+
selectedTargets = [created];
|
|
1666
|
+
} else if (targets.length === 1) {
|
|
1667
|
+
const { confirmed } = await import_inquirer.default.prompt([{
|
|
1573
1668
|
type: "confirm",
|
|
1574
|
-
name: "
|
|
1575
|
-
message: `
|
|
1669
|
+
name: "confirmed",
|
|
1670
|
+
message: `Found 1 project with an internal target \u2014 tunnel "${import_chalk4.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
|
|
1576
1671
|
default: true
|
|
1577
1672
|
}]);
|
|
1578
|
-
if (!
|
|
1579
|
-
console.log(
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
const spinner = !opts.json ? (0, import_ora4.default)("Creating proxy (tenant, keys, dev portal)...").start() : null;
|
|
1584
|
-
let result;
|
|
1585
|
-
try {
|
|
1586
|
-
result = await createProxy({ name, target_url: targetUrl, auth_type: auth, team_id: teamId, ...opts.apiversion ? { api_version: opts.apiversion } : {} });
|
|
1587
|
-
spinner?.succeed(import_chalk6.default.green("Proxy created!"));
|
|
1588
|
-
} catch (err) {
|
|
1589
|
-
spinner?.fail("Failed to create proxy.");
|
|
1590
|
-
throw err;
|
|
1591
|
-
}
|
|
1592
|
-
const version2 = result.api_version || "1.0.0";
|
|
1593
|
-
const keys = result.api_keys ?? {};
|
|
1594
|
-
const adminKey = keys.dev ?? Object.values(keys)[0];
|
|
1595
|
-
const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
|
|
1596
|
-
const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
|
|
1597
|
-
if (opts.json) {
|
|
1598
|
-
process.stdout.write(JSON.stringify({
|
|
1599
|
-
project_id: result.project_id,
|
|
1600
|
-
api_version: version2,
|
|
1601
|
-
proxy_url: proxyUrl,
|
|
1602
|
-
dev_portal: devPortal,
|
|
1603
|
-
api_key: adminKey,
|
|
1604
|
-
api_keys: keys,
|
|
1605
|
-
team_id: teamId
|
|
1606
|
-
}) + "\n");
|
|
1607
|
-
return;
|
|
1608
|
-
}
|
|
1609
|
-
console.log();
|
|
1610
|
-
console.log(` ${import_chalk6.default.dim("Proxy URL: ")} ${import_chalk6.default.bold(proxyUrl)}`);
|
|
1611
|
-
if (devPortal) console.log(` ${import_chalk6.default.dim("Dev portal:")} ${import_chalk6.default.bold(devPortal)}`);
|
|
1612
|
-
if (adminKey) {
|
|
1613
|
-
console.log();
|
|
1614
|
-
console.log(` ${import_chalk6.default.dim("Consumer admin API key (dev):")}`);
|
|
1615
|
-
console.log(` ${import_chalk6.default.bold.green(adminKey)}`);
|
|
1616
|
-
console.log(import_chalk6.default.dim("\n Save this now \u2014 send it as the X-API-Key header. It may not be shown again."));
|
|
1617
|
-
const otherEnvs = Object.keys(keys).filter((e) => e !== "dev");
|
|
1618
|
-
if (otherEnvs.length) {
|
|
1619
|
-
console.log(import_chalk6.default.dim(` (Separate keys were also created for: ${otherEnvs.join(", ")}.)`));
|
|
1673
|
+
if (!confirmed) {
|
|
1674
|
+
console.log("Aborted.");
|
|
1675
|
+
process.exit(0);
|
|
1620
1676
|
}
|
|
1677
|
+
selectedTargets = targets;
|
|
1678
|
+
} else {
|
|
1679
|
+
const ALL = "__all__";
|
|
1680
|
+
const { chosen } = await import_inquirer.default.prompt([{
|
|
1681
|
+
type: "list",
|
|
1682
|
+
name: "chosen",
|
|
1683
|
+
message: `Found ${targets.length} projects with an internal target \u2014 pick one to tunnel:`,
|
|
1684
|
+
choices: [
|
|
1685
|
+
...targets.map((t) => ({
|
|
1686
|
+
name: `${import_chalk4.default.bold(t.projectName)} (${t.tenantName}) \u2014 ${t.target}`,
|
|
1687
|
+
value: t
|
|
1688
|
+
})),
|
|
1689
|
+
new import_inquirer.default.Separator(),
|
|
1690
|
+
{ name: `Tunnel all ${targets.length}`, value: ALL }
|
|
1691
|
+
]
|
|
1692
|
+
}]);
|
|
1693
|
+
selectedTargets = chosen === ALL ? targets : [chosen];
|
|
1621
1694
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1695
|
+
console.log(
|
|
1696
|
+
import_chalk4.default.green(`
|
|
1697
|
+
Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
|
|
1698
|
+
`)
|
|
1699
|
+
);
|
|
1700
|
+
let recordSink;
|
|
1701
|
+
let captureStream;
|
|
1702
|
+
if (options.captureFile) {
|
|
1703
|
+
captureStream = import_fs.default.createWriteStream(options.captureFile, { flags: "a" });
|
|
1704
|
+
recordSink = (r) => captureStream.write(JSON.stringify(r) + "\n");
|
|
1705
|
+
console.log(import_chalk4.default.gray(`Streaming full traffic to ${options.captureFile}
|
|
1706
|
+
`));
|
|
1630
1707
|
}
|
|
1631
|
-
let
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
raw = import_fs2.default.readFileSync(opts.config, "utf8");
|
|
1636
|
-
} catch {
|
|
1637
|
-
fail(`Cannot read --config file: ${opts.config}`);
|
|
1638
|
-
}
|
|
1639
|
-
let parsed;
|
|
1708
|
+
let restore = [];
|
|
1709
|
+
let connect;
|
|
1710
|
+
{
|
|
1711
|
+
const spinner = (0, import_ora2.default)("Registering tunnel with APIblaze...").start();
|
|
1640
1712
|
try {
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1713
|
+
const result = await putDevTunnel({
|
|
1714
|
+
targets: selectedTargets.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId }))
|
|
1715
|
+
});
|
|
1716
|
+
restore = result.restore ?? [];
|
|
1717
|
+
connect = result.connect;
|
|
1718
|
+
spinner.succeed("Tunnel registered.");
|
|
1719
|
+
} catch (err) {
|
|
1720
|
+
spinner.fail("Failed to register tunnel.");
|
|
1721
|
+
throw err;
|
|
1644
1722
|
}
|
|
1645
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) fail("--config must be a JSON object.");
|
|
1646
|
-
body = parsed;
|
|
1647
1723
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1724
|
+
printTunnelEndpoints(restore, selectedTargets);
|
|
1725
|
+
const clients = connect.projects.map(
|
|
1726
|
+
(projectId) => startTunnelClient({
|
|
1727
|
+
connectUrl: connect.url,
|
|
1728
|
+
token: connect.token,
|
|
1729
|
+
projectId,
|
|
1730
|
+
localPort: options.port,
|
|
1731
|
+
onEntry: (entry) => console.log(formatLogLine(entry)),
|
|
1732
|
+
onStatus: (status) => console.log(import_chalk4.default.gray(`[${projectId}] ${status}`)),
|
|
1733
|
+
onCapture: (req, note) => console.log(formatCapturedRequest(req, note)),
|
|
1734
|
+
onCaptureStart: () => console.log(
|
|
1735
|
+
import_chalk4.default.magenta(`
|
|
1736
|
+
\u26B2 No local server on port ${options.port} yet \u2014 capturing requests below. Start your server and they'll forward automatically.
|
|
1737
|
+
`)
|
|
1738
|
+
),
|
|
1739
|
+
onResume: () => console.log(
|
|
1740
|
+
import_chalk4.default.green(`
|
|
1741
|
+
\u2713 Local server detected on port ${options.port} \u2014 forwarding resumed.
|
|
1742
|
+
`)
|
|
1743
|
+
),
|
|
1744
|
+
onRecord: recordSink
|
|
1745
|
+
})
|
|
1746
|
+
);
|
|
1747
|
+
const localUp = await probeLocalServer(options.port);
|
|
1748
|
+
console.log("\n" + import_chalk4.default.gray("\u2500".repeat(60)));
|
|
1749
|
+
console.log(import_chalk4.default.bold("Live traffic") + import_chalk4.default.gray(" (Ctrl+C to stop)"));
|
|
1750
|
+
console.log(
|
|
1751
|
+
localUp ? import_chalk4.default.green(`\u2713 Local server detected on port ${options.port} \u2014 forwarding live.`) : import_chalk4.default.magenta(`\u26B2 Nothing listening on port ${options.port} yet \u2014 requests will be captured until your server starts.`)
|
|
1752
|
+
);
|
|
1753
|
+
console.log(import_chalk4.default.gray("\u2500".repeat(60)) + "\n");
|
|
1754
|
+
let isCleaningUp = false;
|
|
1755
|
+
async function cleanup() {
|
|
1756
|
+
if (isCleaningUp) return;
|
|
1757
|
+
isCleaningUp = true;
|
|
1758
|
+
console.log(import_chalk4.default.gray("\n\nShutting down..."));
|
|
1759
|
+
for (const client of clients) client.close();
|
|
1760
|
+
captureStream?.end();
|
|
1761
|
+
await deleteDevTunnel(restore).catch(() => {
|
|
1762
|
+
});
|
|
1763
|
+
console.log(import_chalk4.default.green("Tunnel stopped."));
|
|
1764
|
+
process.exit(0);
|
|
1651
1765
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1766
|
+
process.on("SIGINT", () => void cleanup());
|
|
1767
|
+
process.on("SIGTERM", () => void cleanup());
|
|
1768
|
+
await new Promise(() => {
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// src/commands/projects.ts
|
|
1773
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1774
|
+
var import_ora3 = __toESM(require("ora"));
|
|
1775
|
+
init_auth();
|
|
1776
|
+
init_api();
|
|
1777
|
+
async function runProjects() {
|
|
1778
|
+
const creds = loadCredentials();
|
|
1779
|
+
if (!creds) {
|
|
1780
|
+
console.error(import_chalk5.default.red("Not logged in. Run `apiblaze login` first."));
|
|
1781
|
+
process.exit(1);
|
|
1782
|
+
}
|
|
1783
|
+
if (creds.githubHandle) {
|
|
1784
|
+
console.log(`${import_chalk5.default.cyan("\u2192")} Logged in as ${import_chalk5.default.bold("@" + creds.githubHandle)}`);
|
|
1785
|
+
}
|
|
1786
|
+
let teamId = creds.teamId;
|
|
1787
|
+
let teamName = creds.teamName;
|
|
1788
|
+
if (!teamId) {
|
|
1789
|
+
const teams = await getTeams().catch(() => []);
|
|
1790
|
+
if (teams.length === 1) {
|
|
1791
|
+
teamId = teams[0].teamId;
|
|
1792
|
+
teamName = teams[0].name;
|
|
1793
|
+
} else if (teams.length > 1) {
|
|
1657
1794
|
const { default: inquirer2 } = await import("inquirer");
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
name = n.length >= 3 ? n : void 0;
|
|
1667
|
-
}
|
|
1668
|
-
for (; ; ) {
|
|
1669
|
-
const { url } = await inquirer2.prompt([{
|
|
1670
|
-
type: "input",
|
|
1671
|
-
name: "url",
|
|
1672
|
-
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
1673
|
-
}]);
|
|
1674
|
-
if (!isHttpUrl(url)) {
|
|
1675
|
-
console.log(import_chalk6.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
1676
|
-
continue;
|
|
1677
|
-
}
|
|
1678
|
-
target = url.trim();
|
|
1679
|
-
break;
|
|
1680
|
-
}
|
|
1681
|
-
} else {
|
|
1682
|
-
fail("A source is required: pass --target, or target/openapi/github in --config.");
|
|
1795
|
+
const { chosen } = await inquirer2.prompt([{
|
|
1796
|
+
type: "list",
|
|
1797
|
+
name: "chosen",
|
|
1798
|
+
message: "Which team do you want to use?",
|
|
1799
|
+
choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
|
|
1800
|
+
}]);
|
|
1801
|
+
teamId = chosen;
|
|
1802
|
+
teamName = teams.find((t) => t.teamId === chosen)?.name;
|
|
1683
1803
|
}
|
|
1684
1804
|
}
|
|
1685
|
-
if (
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
}
|
|
1689
|
-
if (name) {
|
|
1690
|
-
body.name = name;
|
|
1691
|
-
body.subdomain = name;
|
|
1805
|
+
if (!teamId) {
|
|
1806
|
+
console.error(import_chalk5.default.red("No team found. Run `apiblaze login` to set up your team."));
|
|
1807
|
+
process.exit(1);
|
|
1692
1808
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
if (opts.apiversion) body.api_version = opts.apiversion;
|
|
1698
|
-
if (opts.auth && opts.auth !== "api_key") body.auth_type = opts.auth;
|
|
1699
|
-
const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
1700
|
-
if (opts.newSession) clearAnonCred2();
|
|
1701
|
-
const cred = loadAnonCred2();
|
|
1702
|
-
const spinner = !opts.json ? (0, import_ora4.default)(cred ? "Creating proxy (in your anonymous workspace)..." : "Creating proxy...").start() : null;
|
|
1703
|
-
let result;
|
|
1809
|
+
console.log(`${import_chalk5.default.cyan("\u2192")} Team: ${import_chalk5.default.bold(teamName ?? teamId)}
|
|
1810
|
+
`);
|
|
1811
|
+
const spinner = (0, import_ora3.default)("Fetching projects...").start();
|
|
1812
|
+
let projects;
|
|
1704
1813
|
try {
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
if (Array.isArray(result.endpoints)) {
|
|
1708
|
-
result.endpoints = result.endpoints.map((e) => e.replace(/:\/\/[^/]+/, `://${result.project_id}.tryabz.run`));
|
|
1709
|
-
}
|
|
1710
|
-
} else {
|
|
1711
|
-
result = await createProxyAnonymous(body);
|
|
1712
|
-
if (result.cp_key && result.team_id) {
|
|
1713
|
-
saveAnonCred2(result.cp_key, result.team_id, result.claim_code);
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
spinner?.succeed(import_chalk6.default.green("Proxy created!"));
|
|
1814
|
+
projects = await getProjects(teamId);
|
|
1815
|
+
spinner.stop();
|
|
1717
1816
|
} catch (err) {
|
|
1718
|
-
spinner
|
|
1817
|
+
spinner.fail("Failed to fetch projects.");
|
|
1719
1818
|
throw err;
|
|
1720
1819
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
|
|
1724
|
-
const prodEndpoint = (result.endpoints || []).find((e) => e.endsWith("/prod")) || (result.endpoints || [])[0];
|
|
1725
|
-
if (opts.json) {
|
|
1726
|
-
process.stdout.write(JSON.stringify({
|
|
1727
|
-
project_id: result.project_id,
|
|
1728
|
-
api_version: version2,
|
|
1729
|
-
endpoints: result.endpoints,
|
|
1730
|
-
api_key: apiKey,
|
|
1731
|
-
api_keys: keys,
|
|
1732
|
-
claim_url: result.claim_url,
|
|
1733
|
-
anonymous: true
|
|
1734
|
-
}) + "\n");
|
|
1820
|
+
if (projects.length === 0) {
|
|
1821
|
+
console.log(import_chalk5.default.yellow("No projects found for this team."));
|
|
1735
1822
|
return;
|
|
1736
1823
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
if (apiKey) {
|
|
1741
|
-
console.log();
|
|
1742
|
-
console.log(` ${import_chalk6.default.dim("API key:")}`);
|
|
1743
|
-
console.log(` ${import_chalk6.default.bold.green(apiKey)}`);
|
|
1744
|
-
console.log(import_chalk6.default.dim("\n Save this now \u2014 send it as the X-API-Key header. It may not be shown again."));
|
|
1745
|
-
}
|
|
1746
|
-
if (prodEndpoint) printCurlExample(prodEndpoint, opts.auth || "api_key", apiKey, result.portal);
|
|
1747
|
-
const claimCode = result.claim_code || cred?.claim_code;
|
|
1748
|
-
if (claimCode) {
|
|
1749
|
-
console.log();
|
|
1750
|
-
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous \u2014 claim within 30 days or it expires.")} Everything you create`);
|
|
1751
|
-
console.log(` with the CP key shares ONE workspace. To keep it all in one shot:`);
|
|
1752
|
-
console.log(` ${import_chalk6.default.cyan("apiblaze login")} ${import_chalk6.default.dim("(prompts to claim your workspace into your account)")}`);
|
|
1753
|
-
console.log(import_chalk6.default.dim(` From another machine: apiblaze claim ${claimCode} (add --team <name> to merge into an existing team)`));
|
|
1754
|
-
} else if (result.claim_url) {
|
|
1755
|
-
console.log();
|
|
1756
|
-
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous proxy \u2014 claim it to your account within 30 days or it expires:")}`);
|
|
1757
|
-
console.log(` ${import_chalk6.default.bold(result.claim_url)}`);
|
|
1824
|
+
const width = Math.max(...projects.map((p) => p.projectName.length));
|
|
1825
|
+
for (const p of projects) {
|
|
1826
|
+
console.log(` ${import_chalk5.default.bold(p.projectName.padEnd(width))} ${import_chalk5.default.dim("v" + p.apiVersion)}`);
|
|
1758
1827
|
}
|
|
1759
|
-
console.log(
|
|
1828
|
+
console.log(import_chalk5.default.dim(`
|
|
1829
|
+
${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
1760
1830
|
}
|
|
1761
1831
|
|
|
1832
|
+
// src/index.ts
|
|
1833
|
+
init_create();
|
|
1834
|
+
|
|
1762
1835
|
// src/commands/claim.ts
|
|
1763
1836
|
var import_chalk7 = __toESM(require("chalk"));
|
|
1764
1837
|
init_auth();
|
|
@@ -2858,13 +2931,28 @@ var trailingComma = /\s*,\s*/;
|
|
|
2858
2931
|
var parseList = (s) => s.split(trailingComma).map((x) => x.trim()).filter(Boolean);
|
|
2859
2932
|
async function runTenantManage(query, opts) {
|
|
2860
2933
|
const { teamId } = await resolveTeam(opts.team);
|
|
2861
|
-
const slug = opts.tenant ??
|
|
2934
|
+
const slug = opts.tenant ?? await validScopedTenant(teamId, query) ?? await pickTenant(teamId, { message: "Manage which tenant?", initialQuery: query, allowCreate: true });
|
|
2862
2935
|
if (!slug) return;
|
|
2863
2936
|
await tenantHome(teamId, slug);
|
|
2864
2937
|
}
|
|
2865
|
-
function
|
|
2938
|
+
async function validScopedTenant(teamId, query) {
|
|
2866
2939
|
if (query) return void 0;
|
|
2867
|
-
|
|
2940
|
+
const creds = loadCredentials();
|
|
2941
|
+
const scoped = creds?.activeTenant;
|
|
2942
|
+
if (!scoped) return void 0;
|
|
2943
|
+
const out = await admin({
|
|
2944
|
+
method: "GET",
|
|
2945
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants?q=${encodeURIComponent(scoped)}&limit=15`,
|
|
2946
|
+
summary: `Verify tenant scope ${scoped}`
|
|
2947
|
+
}).catch(() => null);
|
|
2948
|
+
const names = (out?.tenants ?? []).map((t) => typeof t === "string" ? t : t.tenant_name);
|
|
2949
|
+
if (names.includes(scoped)) return scoped;
|
|
2950
|
+
const { saveCredentials: saveCredentials2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
2951
|
+
const next = { ...creds };
|
|
2952
|
+
delete next.activeTenant;
|
|
2953
|
+
saveCredentials2(next);
|
|
2954
|
+
console.log(import_chalk24.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
2955
|
+
return void 0;
|
|
2868
2956
|
}
|
|
2869
2957
|
async function tenantHome(teamId, tenant2) {
|
|
2870
2958
|
const { default: inquirer2 } = await import("inquirer");
|
|
@@ -3078,10 +3166,20 @@ async function clientsMenu(teamId, tenant2, base) {
|
|
|
3078
3166
|
}]);
|
|
3079
3167
|
if (pick2 === " back") return;
|
|
3080
3168
|
if (pick2 === " create") {
|
|
3081
|
-
|
|
3169
|
+
let projects = await getProjects(teamId).catch(() => []);
|
|
3082
3170
|
if (!projects.length) {
|
|
3083
|
-
console.log(import_chalk24.default.yellow("
|
|
3084
|
-
|
|
3171
|
+
console.log(import_chalk24.default.yellow("\n An app client needs a proxy to derive its token identity (iss/aud), and this team has none yet."));
|
|
3172
|
+
const { make } = await inquirer2.prompt([{
|
|
3173
|
+
type: "confirm",
|
|
3174
|
+
name: "make",
|
|
3175
|
+
default: true,
|
|
3176
|
+
message: "Create a proxy now (interactive)?"
|
|
3177
|
+
}]);
|
|
3178
|
+
if (!make) continue;
|
|
3179
|
+
const { runCreate: runCreate2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
3180
|
+
await runCreate2({});
|
|
3181
|
+
projects = await getProjects(teamId).catch(() => []);
|
|
3182
|
+
if (!projects.length) continue;
|
|
3085
3183
|
}
|
|
3086
3184
|
const a = await inquirer2.prompt([
|
|
3087
3185
|
{ type: "input", name: "name", message: "Client name:", validate: (s) => !!s.trim() || "required" },
|
|
@@ -3347,6 +3445,7 @@ init_auth();
|
|
|
3347
3445
|
var import_chalk26 = __toESM(require("chalk"));
|
|
3348
3446
|
init_admin();
|
|
3349
3447
|
init_api();
|
|
3448
|
+
init_create();
|
|
3350
3449
|
async function proj(teamId, name, version2) {
|
|
3351
3450
|
return resolveProject(teamId, name, version2);
|
|
3352
3451
|
}
|
|
@@ -5050,16 +5149,25 @@ async function opCall(call) {
|
|
|
5050
5149
|
function printResidue(report, applied) {
|
|
5051
5150
|
const up = report?.upstash ?? {};
|
|
5052
5151
|
const fga = report?.fga ?? {};
|
|
5152
|
+
const ghosts = report?.ghosts ?? {};
|
|
5053
5153
|
console.log(import_chalk33.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
5054
5154
|
console.log(import_chalk33.default.bold("\n Upstash"));
|
|
5055
5155
|
const orphans = up.orphans ?? [];
|
|
5056
5156
|
if (orphans.length === 0) console.log(import_chalk33.default.green(" no orphaned keys"));
|
|
5057
5157
|
for (const o of orphans) console.log(` ${import_chalk33.default.yellow(o.key)} ${import_chalk33.default.dim(`\u2014 ${o.reason}`)}`);
|
|
5058
5158
|
console.log(import_chalk33.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
5159
|
+
if (up.anon_wallet_detail) {
|
|
5160
|
+
const d = up.anon_wallet_detail;
|
|
5161
|
+
console.log(import_chalk33.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
|
|
5162
|
+
}
|
|
5163
|
+
if (up.keyspace_census) {
|
|
5164
|
+
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
5165
|
+
console.log(import_chalk33.default.dim(` keyspace: ${census}`));
|
|
5166
|
+
}
|
|
5059
5167
|
if (up.unknown?.length) console.log(import_chalk33.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
5060
5168
|
if (applied) console.log(` ${import_chalk33.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
5061
5169
|
for (const e of up.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
|
|
5062
|
-
console.log(import_chalk33.default.bold("\n OpenFGA / Neon"));
|
|
5170
|
+
console.log(import_chalk33.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
5063
5171
|
if (applied) {
|
|
5064
5172
|
const swept = fga?.swept ?? [];
|
|
5065
5173
|
if (swept.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
|
|
@@ -5069,6 +5177,8 @@ function printResidue(report, applied) {
|
|
|
5069
5177
|
);
|
|
5070
5178
|
}
|
|
5071
5179
|
if (fga?.remaining) console.log(import_chalk33.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
5180
|
+
const st = fga?.side_tables;
|
|
5181
|
+
if (st) console.log(import_chalk33.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
|
|
5072
5182
|
} else {
|
|
5073
5183
|
const fgaOrphans = fga?.orphans ?? [];
|
|
5074
5184
|
if (fgaOrphans.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
|
|
@@ -5077,8 +5187,26 @@ function printResidue(report, applied) {
|
|
|
5077
5187
|
console.log(` ${import_chalk33.default.yellow(s.store_id)} ${import_chalk33.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
5078
5188
|
}
|
|
5079
5189
|
console.log(import_chalk33.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
5190
|
+
const st = fga?.side_tables;
|
|
5191
|
+
if (st) console.log(import_chalk33.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
|
|
5080
5192
|
}
|
|
5081
5193
|
for (const e of fga?.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
|
|
5194
|
+
console.log(import_chalk33.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
5195
|
+
if (applied) {
|
|
5196
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk33.default.green(" no ghost tuples"));
|
|
5197
|
+
else console.log(` ${import_chalk33.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk33.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
5198
|
+
} else {
|
|
5199
|
+
const n = ghosts?.ghost_count ?? 0;
|
|
5200
|
+
if (n === 0) console.log(import_chalk33.default.green(` no ghost tuples ${import_chalk33.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
5201
|
+
else {
|
|
5202
|
+
console.log(import_chalk33.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
5203
|
+
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
5204
|
+
console.log(import_chalk33.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
5205
|
+
}
|
|
5206
|
+
if (n > 20) console.log(import_chalk33.default.dim(` \u2026 and ${n - 20} more`));
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
|
|
5082
5210
|
console.log();
|
|
5083
5211
|
}
|
|
5084
5212
|
async function runOp(sub, opts = {}) {
|
|
@@ -5096,8 +5224,9 @@ async function runOp(sub, opts = {}) {
|
|
|
5096
5224
|
console.log(import_chalk33.default.bold("\nOperator menu"));
|
|
5097
5225
|
console.log(` ${import_chalk33.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
5098
5226
|
console.log(` ${import_chalk33.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk33.default.dim("-y to skip")})`);
|
|
5099
|
-
console.log(` ${import_chalk33.default.cyan("apiblaze op credits")} list credit wallets
|
|
5100
|
-
`)
|
|
5227
|
+
console.log(` ${import_chalk33.default.cyan("apiblaze op credits")} list credit wallets`);
|
|
5228
|
+
console.log(import_chalk33.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
|
|
5229
|
+
`));
|
|
5101
5230
|
return;
|
|
5102
5231
|
}
|
|
5103
5232
|
case "residue": {
|
|
@@ -5110,15 +5239,20 @@ async function runOp(sub, opts = {}) {
|
|
|
5110
5239
|
const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
|
|
5111
5240
|
const nUp = report?.upstash?.orphans?.length ?? 0;
|
|
5112
5241
|
const nFga = report?.fga?.orphans?.length ?? 0;
|
|
5242
|
+
const nGhost = report?.ghosts?.ghost_count ?? 0;
|
|
5243
|
+
const st = report?.fga?.side_tables ?? {};
|
|
5244
|
+
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
5113
5245
|
printResidue(report, false);
|
|
5114
|
-
if (nUp + nFga === 0) {
|
|
5246
|
+
if (nUp + nFga + nGhost + nSide === 0) {
|
|
5115
5247
|
console.log(import_chalk33.default.green("Nothing to sweep."));
|
|
5116
5248
|
return;
|
|
5117
5249
|
}
|
|
5118
5250
|
if (!opts.yes) {
|
|
5119
5251
|
const readline2 = await import("readline/promises");
|
|
5120
5252
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
5121
|
-
const answer = await rl.question(
|
|
5253
|
+
const answer = await rl.question(
|
|
5254
|
+
import_chalk33.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
|
|
5255
|
+
);
|
|
5122
5256
|
rl.close();
|
|
5123
5257
|
if (answer.trim() !== "sweep") return void console.log(import_chalk33.default.dim("Aborted."));
|
|
5124
5258
|
}
|
|
@@ -5145,6 +5279,7 @@ async function runOp(sub, opts = {}) {
|
|
|
5145
5279
|
|
|
5146
5280
|
// src/index.ts
|
|
5147
5281
|
init_trace();
|
|
5282
|
+
init_auth();
|
|
5148
5283
|
var program = new import_commander.Command();
|
|
5149
5284
|
program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
|
|
5150
5285
|
program.hook("preAction", () => {
|
|
@@ -5158,7 +5293,7 @@ function action(fn) {
|
|
|
5158
5293
|
renderTrace();
|
|
5159
5294
|
} catch (err) {
|
|
5160
5295
|
renderTrace();
|
|
5161
|
-
printError(err);
|
|
5296
|
+
await printError(err);
|
|
5162
5297
|
process.exit(1);
|
|
5163
5298
|
}
|
|
5164
5299
|
};
|
|
@@ -5167,7 +5302,7 @@ program.command("login").description("Authenticate with APIblaze").action(async
|
|
|
5167
5302
|
try {
|
|
5168
5303
|
await runLogin();
|
|
5169
5304
|
} catch (err) {
|
|
5170
|
-
printError(err);
|
|
5305
|
+
await printError(err);
|
|
5171
5306
|
process.exit(1);
|
|
5172
5307
|
}
|
|
5173
5308
|
});
|
|
@@ -5175,7 +5310,7 @@ program.command("create").description("Create a new API proxy (no login needed \
|
|
|
5175
5310
|
try {
|
|
5176
5311
|
await runCreate(opts);
|
|
5177
5312
|
} catch (err) {
|
|
5178
|
-
printError(err);
|
|
5313
|
+
await printError(err);
|
|
5179
5314
|
process.exit(1);
|
|
5180
5315
|
}
|
|
5181
5316
|
});
|
|
@@ -5200,7 +5335,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
5200
5335
|
}
|
|
5201
5336
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
5202
5337
|
} catch (err) {
|
|
5203
|
-
printError(err);
|
|
5338
|
+
await printError(err);
|
|
5204
5339
|
process.exit(1);
|
|
5205
5340
|
}
|
|
5206
5341
|
});
|
|
@@ -5213,7 +5348,7 @@ program.command("whoami").description("Show who you are \u2014 both API Producer
|
|
|
5213
5348
|
try {
|
|
5214
5349
|
await runWhoami(opts);
|
|
5215
5350
|
} catch (err) {
|
|
5216
|
-
printError(err);
|
|
5351
|
+
await printError(err);
|
|
5217
5352
|
process.exit(1);
|
|
5218
5353
|
}
|
|
5219
5354
|
});
|
|
@@ -5221,7 +5356,7 @@ program.command("claim").description("Claim your anonymous workspace into your a
|
|
|
5221
5356
|
try {
|
|
5222
5357
|
await runClaim(code, opts);
|
|
5223
5358
|
} catch (err) {
|
|
5224
|
-
printError(err);
|
|
5359
|
+
await printError(err);
|
|
5225
5360
|
process.exit(1);
|
|
5226
5361
|
}
|
|
5227
5362
|
});
|
|
@@ -5229,7 +5364,7 @@ program.command("team").description("Switch the active team").argument("[team]",
|
|
|
5229
5364
|
try {
|
|
5230
5365
|
await runTeam(team);
|
|
5231
5366
|
} catch (err) {
|
|
5232
|
-
printError(err);
|
|
5367
|
+
await printError(err);
|
|
5233
5368
|
process.exit(1);
|
|
5234
5369
|
}
|
|
5235
5370
|
});
|
|
@@ -5237,7 +5372,7 @@ program.command("projects").description("List the projects in your team").action
|
|
|
5237
5372
|
try {
|
|
5238
5373
|
await runProjects();
|
|
5239
5374
|
} catch (err) {
|
|
5240
|
-
printError(err);
|
|
5375
|
+
await printError(err);
|
|
5241
5376
|
process.exit(1);
|
|
5242
5377
|
}
|
|
5243
5378
|
});
|
|
@@ -5257,7 +5392,7 @@ var tenant = program.command("tenant").description("Manage tenants \u2014 bare c
|
|
|
5257
5392
|
tenant.command("manage").description("Browse & edit one tenant: settings, login app clients, providers, issuers (search-first picker)").argument("[query]", "Search by tenant name/display name (omit to use your tenant scope or pick)").option("--tenant <slug>", "Exact tenant slug (skips the picker)").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantManage(query, opts)));
|
|
5258
5393
|
tenant.command("use").description("Set the sticky tenant scope for future commands (search-first; --clear to unset)").argument("[query]", "Search by tenant name/display name").option("--clear", "Clear the tenant scope").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantUse(query, opts)));
|
|
5259
5394
|
tenant.command("list").description("List tenants in your team (--q searches server-side)").option("--q <search>", "Filter by tenant name or display name").option("--limit <n>", "Page size (max 200)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
|
|
5260
|
-
tenant.command("create").description("Create a tenant in your team").requiredOption("--name <
|
|
5395
|
+
tenant.command("create").description("Create a tenant in your team (tenant names are lowercase alphanumeric and globally unique)").requiredOption("--name <name>", "Tenant name \u2014 becomes the slug ({name}.portal.apiblaze.com) unless --slug overrides; also the display label").option("--slug <tenant_name>", "Explicit tenant slug when it should differ from --name").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
|
|
5261
5396
|
tenant.command("attach").description("Attach a tenant to a proxy").argument("<project>", "Project name or id").requiredOption("--tenant <slug>", "Tenant slug to attach").option("--auth-config <id>", "Auth config id to bind").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTenantAttach(project, opts)));
|
|
5262
5397
|
tenant.command("delete").description("Delete a tenant (full cascade)").argument("<slug>", "Tenant slug to delete").option("--team <id|name>", "Team (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").action(action((slug, opts) => runTenantDelete(slug, opts)));
|
|
5263
5398
|
tenant.command("cors").description("Set the CORS allow-list for a tenant").requiredOption("--tenant <slug>", "Tenant slug").option("--origins <list>", 'Comma-separated origins (or "*"); empty clears').option("--team <id|name>", "Team (defaults to active team)").action(action((opts) => runTenantCors(opts)));
|
|
@@ -5319,14 +5454,32 @@ Examples:
|
|
|
5319
5454
|
$ npx apiblaze throttle myapi --rate 50 --verbose # configure + show the API call
|
|
5320
5455
|
$ npx apiblaze consumer login # act as a consumer of your API
|
|
5321
5456
|
`);
|
|
5322
|
-
function
|
|
5457
|
+
async function recoverStaleTeam() {
|
|
5458
|
+
try {
|
|
5459
|
+
const creds = loadCredentials();
|
|
5460
|
+
if (!creds?.teamId) return;
|
|
5461
|
+
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
5462
|
+
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
5463
|
+
if (!linked) {
|
|
5464
|
+
console.error(import_chalk34.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
if (linked.teamId === creds.teamId) return;
|
|
5468
|
+
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
5469
|
+
delete next.activeTenant;
|
|
5470
|
+
saveCredentials(next);
|
|
5471
|
+
console.error(import_chalk34.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk34.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
5472
|
+
} catch {
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
async function printError(err) {
|
|
5323
5476
|
if (err instanceof ApiError) {
|
|
5324
5477
|
const data = err.body;
|
|
5325
5478
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
5326
5479
|
console.error(import_chalk34.default.red(`
|
|
5327
5480
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
5328
5481
|
if (err.status === 403 || err.status === 404) {
|
|
5329
|
-
|
|
5482
|
+
await recoverStaleTeam();
|
|
5330
5483
|
}
|
|
5331
5484
|
} else if (err instanceof Error) {
|
|
5332
5485
|
console.error(import_chalk34.default.red(`
|