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