apiblaze 0.14.0 → 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 +606 -564
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -370,6 +370,354 @@ var init_anon_cred = __esm({
|
|
|
370
370
|
}
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
+
// src/commands/create.ts
|
|
374
|
+
var create_exports = {};
|
|
375
|
+
__export(create_exports, {
|
|
376
|
+
buildTryItCurl: () => buildTryItCurl,
|
|
377
|
+
runCreate: () => runCreate
|
|
378
|
+
});
|
|
379
|
+
function normalizeName(raw) {
|
|
380
|
+
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
381
|
+
}
|
|
382
|
+
function isHttpUrl(s) {
|
|
383
|
+
try {
|
|
384
|
+
const u = new URL((s || "").trim());
|
|
385
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
386
|
+
} catch {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
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;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
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}"`;
|
|
410
|
+
}
|
|
411
|
+
if (authType === "none") return `curl ${url}`;
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
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
|
+
}
|
|
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.`);
|
|
447
|
+
}
|
|
448
|
+
teamId = match.teamId;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
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
|
+
|
|
373
721
|
// src/lib/trace.ts
|
|
374
722
|
function setVerbose(v) {
|
|
375
723
|
verbose = v;
|
|
@@ -529,22 +877,39 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
529
877
|
q = String(nq ?? "").trim();
|
|
530
878
|
continue;
|
|
531
879
|
}
|
|
532
|
-
return picked;
|
|
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
|
+
}
|
|
533
911
|
}
|
|
534
912
|
}
|
|
535
|
-
async function createTenantInline(teamId) {
|
|
536
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
537
|
-
const { display } = await inquirer2.prompt([{ type: "input", name: "display", message: "Display name for the new tenant:", validate: (s) => !!s.trim() || "required" }]);
|
|
538
|
-
const out = await admin({
|
|
539
|
-
method: "POST",
|
|
540
|
-
path: `/teams/${encodeURIComponent(teamId)}/tenants`,
|
|
541
|
-
body: { display_name: display.trim() },
|
|
542
|
-
summary: `Create tenant "${display.trim()}"`
|
|
543
|
-
});
|
|
544
|
-
const slug = out?.tenant_name ?? out?.tenant?.tenant_name;
|
|
545
|
-
console.log(import_chalk22.default.green(` Tenant ${import_chalk22.default.bold(slug)} created.`));
|
|
546
|
-
return slug;
|
|
547
|
-
}
|
|
548
913
|
var import_chalk22, import_ora8, PAGE;
|
|
549
914
|
var init_tenant_pick = __esm({
|
|
550
915
|
"src/lib/tenant-pick.ts"() {
|
|
@@ -562,7 +927,7 @@ var import_commander = require("commander");
|
|
|
562
927
|
var import_chalk34 = __toESM(require("chalk"));
|
|
563
928
|
|
|
564
929
|
// package.json
|
|
565
|
-
var version = "0.
|
|
930
|
+
var version = "0.15.0";
|
|
566
931
|
|
|
567
932
|
// src/index.ts
|
|
568
933
|
init_types();
|
|
@@ -1217,590 +1582,256 @@ async function offerAutoCreate(teamId, port) {
|
|
|
1217
1582
|
console.log(import_chalk4.default.dim(" Send it as the X-API-Key header. It may not be shown again."));
|
|
1218
1583
|
}
|
|
1219
1584
|
}
|
|
1220
|
-
const targets = await getLocalhostTargets(teamId).catch(() => []);
|
|
1221
|
-
const created = targets.find((t) => t.projectId === result.project_id);
|
|
1222
|
-
if (!created) {
|
|
1223
|
-
console.log(import_chalk4.default.yellow(" Proxy created, but it did not appear as a localhost target \u2014 try `apiblaze dev` again."));
|
|
1224
|
-
return null;
|
|
1225
|
-
}
|
|
1226
|
-
return created;
|
|
1227
|
-
}
|
|
1228
|
-
function isInternalTarget(url) {
|
|
1229
|
-
if (!url) return false;
|
|
1230
|
-
try {
|
|
1231
|
-
const h = new URL(url).hostname.toLowerCase();
|
|
1232
|
-
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);
|
|
1233
|
-
} catch {
|
|
1234
|
-
return false;
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
function printTunnelEndpoints(restore, targets) {
|
|
1238
|
-
if (restore.length === 0) return;
|
|
1239
|
-
console.log(import_chalk4.default.bold("\nYour proxy is live at:"));
|
|
1240
|
-
for (const r of restore) {
|
|
1241
|
-
const label2 = targets.find((t) => t.projectId === r.projectId)?.projectName ?? r.projectId;
|
|
1242
|
-
console.log(`
|
|
1243
|
-
${import_chalk4.default.bold(label2)}`);
|
|
1244
|
-
const internalEnvs = Object.keys(r.environments ?? {}).filter((e) => isInternalTarget(r.environments[e]?.target));
|
|
1245
|
-
const envs = internalEnvs.includes("dev") ? ["dev"] : internalEnvs.length ? internalEnvs : ["dev"];
|
|
1246
|
-
for (const env of envs) {
|
|
1247
|
-
console.log(` ${import_chalk4.default.dim("API: ")} ${import_chalk4.default.cyan(`https://${r.projectId}.abz.run/${r.apiVersion}/${env}/`)}`);
|
|
1248
|
-
}
|
|
1249
|
-
if (r.tenant) {
|
|
1250
|
-
console.log(` ${import_chalk4.default.dim("Portal:")} ${import_chalk4.default.cyan(`https://${r.tenant}.portal.apiblaze.com/${r.apiVersion}`)}`);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
async function probeLocalServer(port) {
|
|
1255
|
-
const controller = new AbortController();
|
|
1256
|
-
const timer = setTimeout(() => controller.abort(), 1500);
|
|
1257
|
-
try {
|
|
1258
|
-
await fetch(`http://127.0.0.1:${port}/`, { method: "HEAD", signal: controller.signal });
|
|
1259
|
-
return true;
|
|
1260
|
-
} catch (err) {
|
|
1261
|
-
if (err?.name === "AbortError") return true;
|
|
1262
|
-
const code = err?.cause?.code;
|
|
1263
|
-
return !(code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EHOSTUNREACH");
|
|
1264
|
-
} finally {
|
|
1265
|
-
clearTimeout(timer);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
async function runDev(options) {
|
|
1269
|
-
const creds = await ensureLoggedIn(!!process.stdin.isTTY);
|
|
1270
|
-
const linked = await resolveLinkedTeam({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
1271
|
-
if (!linked) {
|
|
1272
|
-
console.error(import_chalk4.default.red("No team available. Run `apiblaze login` to set up your team."));
|
|
1273
|
-
process.exit(1);
|
|
1274
|
-
}
|
|
1275
|
-
const teamId = linked.teamId;
|
|
1276
|
-
if (linked.teamId !== creds.teamId || linked.teamName !== creds.teamName) {
|
|
1277
|
-
saveCredentials({ ...creds, teamId: linked.teamId, teamName: linked.teamName });
|
|
1278
|
-
}
|
|
1279
|
-
if (linked.teamName) {
|
|
1280
|
-
console.log(`${import_chalk4.default.cyan("\u2192")} Team: ${import_chalk4.default.bold(linked.teamName)}`);
|
|
1281
|
-
}
|
|
1282
|
-
let targets;
|
|
1283
|
-
{
|
|
1284
|
-
const spinner = (0, import_ora2.default)("Fetching your localhost projects...").start();
|
|
1285
|
-
try {
|
|
1286
|
-
targets = await getLocalhostTargets(teamId);
|
|
1287
|
-
spinner.stop();
|
|
1288
|
-
} catch (err) {
|
|
1289
|
-
spinner.fail("Failed to fetch projects.");
|
|
1290
|
-
throw err;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
let selectedTargets;
|
|
1294
|
-
if (targets.length === 0) {
|
|
1295
|
-
const created = await offerAutoCreate(teamId, options.port);
|
|
1296
|
-
if (!created) {
|
|
1297
|
-
console.log("Set a project's upstream target to localhost or a private IP, then try again.");
|
|
1298
|
-
process.exit(0);
|
|
1299
|
-
}
|
|
1300
|
-
selectedTargets = [created];
|
|
1301
|
-
} else if (targets.length === 1) {
|
|
1302
|
-
const { confirmed } = await import_inquirer.default.prompt([{
|
|
1303
|
-
type: "confirm",
|
|
1304
|
-
name: "confirmed",
|
|
1305
|
-
message: `Found 1 project with an internal target \u2014 tunnel "${import_chalk4.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
|
|
1306
|
-
default: true
|
|
1307
|
-
}]);
|
|
1308
|
-
if (!confirmed) {
|
|
1309
|
-
console.log("Aborted.");
|
|
1310
|
-
process.exit(0);
|
|
1311
|
-
}
|
|
1312
|
-
selectedTargets = targets;
|
|
1313
|
-
} else {
|
|
1314
|
-
const ALL = "__all__";
|
|
1315
|
-
const { chosen } = await import_inquirer.default.prompt([{
|
|
1316
|
-
type: "list",
|
|
1317
|
-
name: "chosen",
|
|
1318
|
-
message: `Found ${targets.length} projects with an internal target \u2014 pick one to tunnel:`,
|
|
1319
|
-
choices: [
|
|
1320
|
-
...targets.map((t) => ({
|
|
1321
|
-
name: `${import_chalk4.default.bold(t.projectName)} (${t.tenantName}) \u2014 ${t.target}`,
|
|
1322
|
-
value: t
|
|
1323
|
-
})),
|
|
1324
|
-
new import_inquirer.default.Separator(),
|
|
1325
|
-
{ name: `Tunnel all ${targets.length}`, value: ALL }
|
|
1326
|
-
]
|
|
1327
|
-
}]);
|
|
1328
|
-
selectedTargets = chosen === ALL ? targets : [chosen];
|
|
1329
|
-
}
|
|
1330
|
-
console.log(
|
|
1331
|
-
import_chalk4.default.green(`
|
|
1332
|
-
Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
|
|
1333
|
-
`)
|
|
1334
|
-
);
|
|
1335
|
-
let recordSink;
|
|
1336
|
-
let captureStream;
|
|
1337
|
-
if (options.captureFile) {
|
|
1338
|
-
captureStream = import_fs.default.createWriteStream(options.captureFile, { flags: "a" });
|
|
1339
|
-
recordSink = (r) => captureStream.write(JSON.stringify(r) + "\n");
|
|
1340
|
-
console.log(import_chalk4.default.gray(`Streaming full traffic to ${options.captureFile}
|
|
1341
|
-
`));
|
|
1342
|
-
}
|
|
1343
|
-
let restore = [];
|
|
1344
|
-
let connect;
|
|
1345
|
-
{
|
|
1346
|
-
const spinner = (0, import_ora2.default)("Registering tunnel with APIblaze...").start();
|
|
1347
|
-
try {
|
|
1348
|
-
const result = await putDevTunnel({
|
|
1349
|
-
targets: selectedTargets.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId }))
|
|
1350
|
-
});
|
|
1351
|
-
restore = result.restore ?? [];
|
|
1352
|
-
connect = result.connect;
|
|
1353
|
-
spinner.succeed("Tunnel registered.");
|
|
1354
|
-
} catch (err) {
|
|
1355
|
-
spinner.fail("Failed to register tunnel.");
|
|
1356
|
-
throw err;
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
printTunnelEndpoints(restore, selectedTargets);
|
|
1360
|
-
const clients = connect.projects.map(
|
|
1361
|
-
(projectId) => startTunnelClient({
|
|
1362
|
-
connectUrl: connect.url,
|
|
1363
|
-
token: connect.token,
|
|
1364
|
-
projectId,
|
|
1365
|
-
localPort: options.port,
|
|
1366
|
-
onEntry: (entry) => console.log(formatLogLine(entry)),
|
|
1367
|
-
onStatus: (status) => console.log(import_chalk4.default.gray(`[${projectId}] ${status}`)),
|
|
1368
|
-
onCapture: (req, note) => console.log(formatCapturedRequest(req, note)),
|
|
1369
|
-
onCaptureStart: () => console.log(
|
|
1370
|
-
import_chalk4.default.magenta(`
|
|
1371
|
-
\u26B2 No local server on port ${options.port} yet \u2014 capturing requests below. Start your server and they'll forward automatically.
|
|
1372
|
-
`)
|
|
1373
|
-
),
|
|
1374
|
-
onResume: () => console.log(
|
|
1375
|
-
import_chalk4.default.green(`
|
|
1376
|
-
\u2713 Local server detected on port ${options.port} \u2014 forwarding resumed.
|
|
1377
|
-
`)
|
|
1378
|
-
),
|
|
1379
|
-
onRecord: recordSink
|
|
1380
|
-
})
|
|
1381
|
-
);
|
|
1382
|
-
const localUp = await probeLocalServer(options.port);
|
|
1383
|
-
console.log("\n" + import_chalk4.default.gray("\u2500".repeat(60)));
|
|
1384
|
-
console.log(import_chalk4.default.bold("Live traffic") + import_chalk4.default.gray(" (Ctrl+C to stop)"));
|
|
1385
|
-
console.log(
|
|
1386
|
-
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.`)
|
|
1387
|
-
);
|
|
1388
|
-
console.log(import_chalk4.default.gray("\u2500".repeat(60)) + "\n");
|
|
1389
|
-
let isCleaningUp = false;
|
|
1390
|
-
async function cleanup() {
|
|
1391
|
-
if (isCleaningUp) return;
|
|
1392
|
-
isCleaningUp = true;
|
|
1393
|
-
console.log(import_chalk4.default.gray("\n\nShutting down..."));
|
|
1394
|
-
for (const client of clients) client.close();
|
|
1395
|
-
captureStream?.end();
|
|
1396
|
-
await deleteDevTunnel(restore).catch(() => {
|
|
1397
|
-
});
|
|
1398
|
-
console.log(import_chalk4.default.green("Tunnel stopped."));
|
|
1399
|
-
process.exit(0);
|
|
1400
|
-
}
|
|
1401
|
-
process.on("SIGINT", () => void cleanup());
|
|
1402
|
-
process.on("SIGTERM", () => void cleanup());
|
|
1403
|
-
await new Promise(() => {
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// src/commands/projects.ts
|
|
1408
|
-
var import_chalk5 = __toESM(require("chalk"));
|
|
1409
|
-
var import_ora3 = __toESM(require("ora"));
|
|
1410
|
-
init_auth();
|
|
1411
|
-
init_api();
|
|
1412
|
-
async function runProjects() {
|
|
1413
|
-
const creds = loadCredentials();
|
|
1414
|
-
if (!creds) {
|
|
1415
|
-
console.error(import_chalk5.default.red("Not logged in. Run `apiblaze login` first."));
|
|
1416
|
-
process.exit(1);
|
|
1417
|
-
}
|
|
1418
|
-
if (creds.githubHandle) {
|
|
1419
|
-
console.log(`${import_chalk5.default.cyan("\u2192")} Logged in as ${import_chalk5.default.bold("@" + creds.githubHandle)}`);
|
|
1420
|
-
}
|
|
1421
|
-
let teamId = creds.teamId;
|
|
1422
|
-
let teamName = creds.teamName;
|
|
1423
|
-
if (!teamId) {
|
|
1424
|
-
const teams = await getTeams().catch(() => []);
|
|
1425
|
-
if (teams.length === 1) {
|
|
1426
|
-
teamId = teams[0].teamId;
|
|
1427
|
-
teamName = teams[0].name;
|
|
1428
|
-
} else if (teams.length > 1) {
|
|
1429
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
1430
|
-
const { chosen } = await inquirer2.prompt([{
|
|
1431
|
-
type: "list",
|
|
1432
|
-
name: "chosen",
|
|
1433
|
-
message: "Which team do you want to use?",
|
|
1434
|
-
choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
|
|
1435
|
-
}]);
|
|
1436
|
-
teamId = chosen;
|
|
1437
|
-
teamName = teams.find((t) => t.teamId === chosen)?.name;
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
if (!teamId) {
|
|
1441
|
-
console.error(import_chalk5.default.red("No team found. Run `apiblaze login` to set up your team."));
|
|
1442
|
-
process.exit(1);
|
|
1443
|
-
}
|
|
1444
|
-
console.log(`${import_chalk5.default.cyan("\u2192")} Team: ${import_chalk5.default.bold(teamName ?? teamId)}
|
|
1445
|
-
`);
|
|
1446
|
-
const spinner = (0, import_ora3.default)("Fetching projects...").start();
|
|
1447
|
-
let projects;
|
|
1448
|
-
try {
|
|
1449
|
-
projects = await getProjects(teamId);
|
|
1450
|
-
spinner.stop();
|
|
1451
|
-
} catch (err) {
|
|
1452
|
-
spinner.fail("Failed to fetch projects.");
|
|
1453
|
-
throw err;
|
|
1454
|
-
}
|
|
1455
|
-
if (projects.length === 0) {
|
|
1456
|
-
console.log(import_chalk5.default.yellow("No projects found for this team."));
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
const width = Math.max(...projects.map((p) => p.projectName.length));
|
|
1460
|
-
for (const p of projects) {
|
|
1461
|
-
console.log(` ${import_chalk5.default.bold(p.projectName.padEnd(width))} ${import_chalk5.default.dim("v" + p.apiVersion)}`);
|
|
1462
|
-
}
|
|
1463
|
-
console.log(import_chalk5.default.dim(`
|
|
1464
|
-
${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// src/commands/create.ts
|
|
1468
|
-
var import_fs2 = __toESM(require("fs"));
|
|
1469
|
-
var import_chalk6 = __toESM(require("chalk"));
|
|
1470
|
-
var import_ora4 = __toESM(require("ora"));
|
|
1471
|
-
init_auth();
|
|
1472
|
-
init_api();
|
|
1473
|
-
function normalizeName(raw) {
|
|
1474
|
-
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1475
|
-
}
|
|
1476
|
-
function isHttpUrl(s) {
|
|
1477
|
-
try {
|
|
1478
|
-
const u = new URL((s || "").trim());
|
|
1479
|
-
return u.protocol === "http:" || u.protocol === "https:";
|
|
1480
|
-
} catch {
|
|
1481
|
-
return false;
|
|
1585
|
+
const targets = await getLocalhostTargets(teamId).catch(() => []);
|
|
1586
|
+
const created = targets.find((t) => t.projectId === result.project_id);
|
|
1587
|
+
if (!created) {
|
|
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;
|
|
1482
1590
|
}
|
|
1591
|
+
return created;
|
|
1483
1592
|
}
|
|
1484
|
-
function
|
|
1593
|
+
function isInternalTarget(url) {
|
|
1594
|
+
if (!url) return false;
|
|
1485
1595
|
try {
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
if (dot < 0) return devPortal;
|
|
1489
|
-
const product = u.hostname.slice(0, dot).split("-")[0];
|
|
1490
|
-
u.hostname = `${product}${u.hostname.slice(dot)}`;
|
|
1491
|
-
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);
|
|
1492
1598
|
} catch {
|
|
1493
|
-
return
|
|
1599
|
+
return false;
|
|
1494
1600
|
}
|
|
1495
1601
|
}
|
|
1496
|
-
function
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
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
|
+
}
|
|
1504
1617
|
}
|
|
1505
|
-
if (authType === "none") return `curl ${url}`;
|
|
1506
|
-
return null;
|
|
1507
1618
|
}
|
|
1508
|
-
function
|
|
1509
|
-
const
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
|
|
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);
|
|
1517
1631
|
}
|
|
1518
1632
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const
|
|
1522
|
-
if (!
|
|
1523
|
-
|
|
1524
|
-
|
|
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);
|
|
1525
1639
|
}
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
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 });
|
|
1530
1643
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
if (opts.team.startsWith("team_")) {
|
|
1534
|
-
teamId = opts.team;
|
|
1535
|
-
} else {
|
|
1536
|
-
const teams = await getTeams().catch(() => []);
|
|
1537
|
-
const match = teams.find(
|
|
1538
|
-
(t) => t.teamId === opts.team || t.name.toLowerCase() === opts.team.toLowerCase()
|
|
1539
|
-
);
|
|
1540
|
-
if (!match) {
|
|
1541
|
-
fail(`Team "${opts.team}" not found. Run \`apiblaze team\` to see your teams.`);
|
|
1542
|
-
}
|
|
1543
|
-
teamId = match.teamId;
|
|
1544
|
-
}
|
|
1644
|
+
if (linked.teamName) {
|
|
1645
|
+
console.log(`${import_chalk4.default.cyan("\u2192")} Team: ${import_chalk4.default.bold(linked.teamName)}`);
|
|
1545
1646
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
fail(
|
|
1554
|
-
|
|
1555
|
-
} else if (interactive) {
|
|
1556
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
1557
|
-
for (; ; ) {
|
|
1558
|
-
const { rawName } = await inquirer2.prompt([{
|
|
1559
|
-
type: "input",
|
|
1560
|
-
name: "rawName",
|
|
1561
|
-
message: "Proxy name (your API will live at <name>.abz.run):",
|
|
1562
|
-
transformer: (v) => normalizeName(v)
|
|
1563
|
-
}]);
|
|
1564
|
-
name = normalizeName(rawName);
|
|
1565
|
-
if (name.length < 3) {
|
|
1566
|
-
console.log(import_chalk6.default.yellow(" Name must be at least 3 characters (letters and digits only).\n"));
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
const spinner2 = (0, import_ora4.default)("Checking availability...").start();
|
|
1570
|
-
try {
|
|
1571
|
-
const check = await checkProxyName(name, teamId, opts.apiversion);
|
|
1572
|
-
spinner2.stop();
|
|
1573
|
-
if (!check.canUseProjectName || !check.canUseApiVersion) {
|
|
1574
|
-
console.log(import_chalk6.default.yellow(` "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}. Try another.
|
|
1575
|
-
`));
|
|
1576
|
-
continue;
|
|
1577
|
-
}
|
|
1578
|
-
} catch {
|
|
1579
|
-
spinner2.stop();
|
|
1580
|
-
console.log(import_chalk6.default.dim(" (could not verify availability; continuing)"));
|
|
1581
|
-
}
|
|
1582
|
-
console.log(`${import_chalk6.default.cyan("\u2192")} Your API will live at ${import_chalk6.default.bold(`https://${name}.abz.run`)}
|
|
1583
|
-
`);
|
|
1584
|
-
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;
|
|
1585
1656
|
}
|
|
1586
|
-
} else {
|
|
1587
|
-
fail("--name is required in non-interactive mode.");
|
|
1588
1657
|
}
|
|
1589
|
-
let
|
|
1590
|
-
if (
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
for (; ; ) {
|
|
1596
|
-
const { url } = await inquirer2.prompt([{
|
|
1597
|
-
type: "input",
|
|
1598
|
-
name: "url",
|
|
1599
|
-
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
1600
|
-
}]);
|
|
1601
|
-
if (!isHttpUrl(url)) {
|
|
1602
|
-
console.log(import_chalk6.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
1603
|
-
continue;
|
|
1604
|
-
}
|
|
1605
|
-
targetUrl = url.trim();
|
|
1606
|
-
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);
|
|
1607
1664
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
if (interactive && !opts.yes) {
|
|
1612
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
1613
|
-
console.log(`${import_chalk6.default.cyan("\u2192")} Auth: ${import_chalk6.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
|
|
1614
|
-
const { ok } = await inquirer2.prompt([{
|
|
1665
|
+
selectedTargets = [created];
|
|
1666
|
+
} else if (targets.length === 1) {
|
|
1667
|
+
const { confirmed } = await import_inquirer.default.prompt([{
|
|
1615
1668
|
type: "confirm",
|
|
1616
|
-
name: "
|
|
1617
|
-
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})?`,
|
|
1618
1671
|
default: true
|
|
1619
1672
|
}]);
|
|
1620
|
-
if (!
|
|
1621
|
-
console.log(
|
|
1622
|
-
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
const spinner = !opts.json ? (0, import_ora4.default)("Creating proxy (tenant, keys, dev portal)...").start() : null;
|
|
1626
|
-
let result;
|
|
1627
|
-
try {
|
|
1628
|
-
result = await createProxy({ name, target_url: targetUrl, auth_type: auth, team_id: teamId, ...opts.apiversion ? { api_version: opts.apiversion } : {} });
|
|
1629
|
-
spinner?.succeed(import_chalk6.default.green("Proxy created!"));
|
|
1630
|
-
} catch (err) {
|
|
1631
|
-
spinner?.fail("Failed to create proxy.");
|
|
1632
|
-
throw err;
|
|
1633
|
-
}
|
|
1634
|
-
const version2 = result.api_version || "1.0.0";
|
|
1635
|
-
const keys = result.api_keys ?? {};
|
|
1636
|
-
const adminKey = keys.dev ?? Object.values(keys)[0];
|
|
1637
|
-
const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
|
|
1638
|
-
const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
|
|
1639
|
-
if (opts.json) {
|
|
1640
|
-
process.stdout.write(JSON.stringify({
|
|
1641
|
-
project_id: result.project_id,
|
|
1642
|
-
api_version: version2,
|
|
1643
|
-
proxy_url: proxyUrl,
|
|
1644
|
-
dev_portal: devPortal,
|
|
1645
|
-
api_key: adminKey,
|
|
1646
|
-
api_keys: keys,
|
|
1647
|
-
team_id: teamId
|
|
1648
|
-
}) + "\n");
|
|
1649
|
-
return;
|
|
1650
|
-
}
|
|
1651
|
-
console.log();
|
|
1652
|
-
console.log(` ${import_chalk6.default.dim("Proxy URL: ")} ${import_chalk6.default.bold(proxyUrl)}`);
|
|
1653
|
-
if (devPortal) console.log(` ${import_chalk6.default.dim("Dev portal:")} ${import_chalk6.default.bold(devPortal)}`);
|
|
1654
|
-
if (adminKey) {
|
|
1655
|
-
console.log();
|
|
1656
|
-
console.log(` ${import_chalk6.default.dim("Consumer admin API key (dev):")}`);
|
|
1657
|
-
console.log(` ${import_chalk6.default.bold.green(adminKey)}`);
|
|
1658
|
-
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."));
|
|
1659
|
-
const otherEnvs = Object.keys(keys).filter((e) => e !== "dev");
|
|
1660
|
-
if (otherEnvs.length) {
|
|
1661
|
-
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);
|
|
1662
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];
|
|
1663
1694
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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
|
+
`));
|
|
1672
1707
|
}
|
|
1673
|
-
let
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
raw = import_fs2.default.readFileSync(opts.config, "utf8");
|
|
1678
|
-
} catch {
|
|
1679
|
-
fail(`Cannot read --config file: ${opts.config}`);
|
|
1680
|
-
}
|
|
1681
|
-
let parsed;
|
|
1708
|
+
let restore = [];
|
|
1709
|
+
let connect;
|
|
1710
|
+
{
|
|
1711
|
+
const spinner = (0, import_ora2.default)("Registering tunnel with APIblaze...").start();
|
|
1682
1712
|
try {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
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;
|
|
1686
1722
|
}
|
|
1687
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) fail("--config must be a JSON object.");
|
|
1688
|
-
body = parsed;
|
|
1689
1723
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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);
|
|
1693
1765
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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) {
|
|
1699
1794
|
const { default: inquirer2 } = await import("inquirer");
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
name = n.length >= 3 ? n : void 0;
|
|
1709
|
-
}
|
|
1710
|
-
for (; ; ) {
|
|
1711
|
-
const { url } = await inquirer2.prompt([{
|
|
1712
|
-
type: "input",
|
|
1713
|
-
name: "url",
|
|
1714
|
-
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
1715
|
-
}]);
|
|
1716
|
-
if (!isHttpUrl(url)) {
|
|
1717
|
-
console.log(import_chalk6.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
1718
|
-
continue;
|
|
1719
|
-
}
|
|
1720
|
-
target = url.trim();
|
|
1721
|
-
break;
|
|
1722
|
-
}
|
|
1723
|
-
} else {
|
|
1724
|
-
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;
|
|
1725
1803
|
}
|
|
1726
1804
|
}
|
|
1727
|
-
if (
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
}
|
|
1731
|
-
if (name) {
|
|
1732
|
-
body.name = name;
|
|
1733
|
-
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);
|
|
1734
1808
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
if (opts.apiversion) body.api_version = opts.apiversion;
|
|
1740
|
-
if (opts.auth && opts.auth !== "api_key") body.auth_type = opts.auth;
|
|
1741
|
-
const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
1742
|
-
if (opts.newSession) clearAnonCred2();
|
|
1743
|
-
const cred = loadAnonCred2();
|
|
1744
|
-
const spinner = !opts.json ? (0, import_ora4.default)(cred ? "Creating proxy (in your anonymous workspace)..." : "Creating proxy...").start() : null;
|
|
1745
|
-
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;
|
|
1746
1813
|
try {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
if (Array.isArray(result.endpoints)) {
|
|
1750
|
-
result.endpoints = result.endpoints.map((e) => e.replace(/:\/\/[^/]+/, `://${result.project_id}.tryabz.run`));
|
|
1751
|
-
}
|
|
1752
|
-
} else {
|
|
1753
|
-
result = await createProxyAnonymous(body);
|
|
1754
|
-
if (result.cp_key && result.team_id) {
|
|
1755
|
-
saveAnonCred2(result.cp_key, result.team_id, result.claim_code);
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
spinner?.succeed(import_chalk6.default.green("Proxy created!"));
|
|
1814
|
+
projects = await getProjects(teamId);
|
|
1815
|
+
spinner.stop();
|
|
1759
1816
|
} catch (err) {
|
|
1760
|
-
spinner
|
|
1817
|
+
spinner.fail("Failed to fetch projects.");
|
|
1761
1818
|
throw err;
|
|
1762
1819
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
|
|
1766
|
-
const prodEndpoint = (result.endpoints || []).find((e) => e.endsWith("/prod")) || (result.endpoints || [])[0];
|
|
1767
|
-
if (opts.json) {
|
|
1768
|
-
process.stdout.write(JSON.stringify({
|
|
1769
|
-
project_id: result.project_id,
|
|
1770
|
-
api_version: version2,
|
|
1771
|
-
endpoints: result.endpoints,
|
|
1772
|
-
api_key: apiKey,
|
|
1773
|
-
api_keys: keys,
|
|
1774
|
-
claim_url: result.claim_url,
|
|
1775
|
-
anonymous: true
|
|
1776
|
-
}) + "\n");
|
|
1820
|
+
if (projects.length === 0) {
|
|
1821
|
+
console.log(import_chalk5.default.yellow("No projects found for this team."));
|
|
1777
1822
|
return;
|
|
1778
1823
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
if (apiKey) {
|
|
1783
|
-
console.log();
|
|
1784
|
-
console.log(` ${import_chalk6.default.dim("API key:")}`);
|
|
1785
|
-
console.log(` ${import_chalk6.default.bold.green(apiKey)}`);
|
|
1786
|
-
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."));
|
|
1787
|
-
}
|
|
1788
|
-
if (prodEndpoint) printCurlExample(prodEndpoint, opts.auth || "api_key", apiKey, result.portal);
|
|
1789
|
-
const claimCode = result.claim_code || cred?.claim_code;
|
|
1790
|
-
if (claimCode) {
|
|
1791
|
-
console.log();
|
|
1792
|
-
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous \u2014 claim within 30 days or it expires.")} Everything you create`);
|
|
1793
|
-
console.log(` with the CP key shares ONE workspace. To keep it all in one shot:`);
|
|
1794
|
-
console.log(` ${import_chalk6.default.cyan("apiblaze login")} ${import_chalk6.default.dim("(prompts to claim your workspace into your account)")}`);
|
|
1795
|
-
console.log(import_chalk6.default.dim(` From another machine: apiblaze claim ${claimCode} (add --team <name> to merge into an existing team)`));
|
|
1796
|
-
} else if (result.claim_url) {
|
|
1797
|
-
console.log();
|
|
1798
|
-
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous proxy \u2014 claim it to your account within 30 days or it expires:")}`);
|
|
1799
|
-
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)}`);
|
|
1800
1827
|
}
|
|
1801
|
-
console.log(
|
|
1828
|
+
console.log(import_chalk5.default.dim(`
|
|
1829
|
+
${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
1802
1830
|
}
|
|
1803
1831
|
|
|
1832
|
+
// src/index.ts
|
|
1833
|
+
init_create();
|
|
1834
|
+
|
|
1804
1835
|
// src/commands/claim.ts
|
|
1805
1836
|
var import_chalk7 = __toESM(require("chalk"));
|
|
1806
1837
|
init_auth();
|
|
@@ -3135,10 +3166,20 @@ async function clientsMenu(teamId, tenant2, base) {
|
|
|
3135
3166
|
}]);
|
|
3136
3167
|
if (pick2 === " back") return;
|
|
3137
3168
|
if (pick2 === " create") {
|
|
3138
|
-
|
|
3169
|
+
let projects = await getProjects(teamId).catch(() => []);
|
|
3139
3170
|
if (!projects.length) {
|
|
3140
|
-
console.log(import_chalk24.default.yellow("
|
|
3141
|
-
|
|
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;
|
|
3142
3183
|
}
|
|
3143
3184
|
const a = await inquirer2.prompt([
|
|
3144
3185
|
{ type: "input", name: "name", message: "Client name:", validate: (s) => !!s.trim() || "required" },
|
|
@@ -3404,6 +3445,7 @@ init_auth();
|
|
|
3404
3445
|
var import_chalk26 = __toESM(require("chalk"));
|
|
3405
3446
|
init_admin();
|
|
3406
3447
|
init_api();
|
|
3448
|
+
init_create();
|
|
3407
3449
|
async function proj(teamId, name, version2) {
|
|
3408
3450
|
return resolveProject(teamId, name, version2);
|
|
3409
3451
|
}
|
|
@@ -5350,7 +5392,7 @@ var tenant = program.command("tenant").description("Manage tenants \u2014 bare c
|
|
|
5350
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)));
|
|
5351
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)));
|
|
5352
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)));
|
|
5353
|
-
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)));
|
|
5354
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)));
|
|
5355
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)));
|
|
5356
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)));
|
package/package.json
CHANGED