apiblaze 0.11.0 → 0.11.1

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.
Files changed (2) hide show
  1. package/dist/index.js +133 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -305,7 +305,7 @@ var import_commander = require("commander");
305
305
  var import_chalk32 = __toESM(require("chalk"));
306
306
 
307
307
  // package.json
308
- var version = "0.11.0";
308
+ var version = "0.11.1";
309
309
 
310
310
  // src/index.ts
311
311
  init_types();
@@ -3418,6 +3418,102 @@ async function settingsGroup(proj2, cfg, group) {
3418
3418
  cfg = await fetchConfigBlob(proj2);
3419
3419
  }
3420
3420
  }
3421
+ var FN_CHOICES = [
3422
+ { name: "uppercase", value: "uppercase" },
3423
+ { name: "lowercase", value: "lowercase" },
3424
+ { name: "trim", value: "trim" },
3425
+ { name: "url_encode", value: "url_encode" },
3426
+ { name: "url_decode", value: "url_decode" },
3427
+ { name: "base64url_encode", value: "base64url_encode" },
3428
+ { name: "base64url_decode", value: "base64url_decode" },
3429
+ { name: "hash \u2014 sha256/md5 digest", value: "hash" },
3430
+ { name: "regex_extract \u2014 pull out a capture group", value: "regex_extract" },
3431
+ { name: "concat \u2014 prepend/append text", value: "concat" }
3432
+ ];
3433
+ async function buildFns(which) {
3434
+ const { default: inquirer2 } = await import("inquirer");
3435
+ const fns = [];
3436
+ for (; ; ) {
3437
+ const { fn } = await inquirer2.prompt([{
3438
+ type: "list",
3439
+ name: "fn",
3440
+ message: fns.length ? `${which}: ${fns.map((f) => f.fn).join(" \u2192 ")} \u2014 add another?` : `${which} \u2014 add a function?`,
3441
+ choices: [{ name: fns.length ? "\u2713 Done" : "No functions", value: null }, ...FN_CHOICES]
3442
+ }]);
3443
+ if (!fn) return fns;
3444
+ if (fn === "hash") {
3445
+ const { algorithm } = await inquirer2.prompt([{ type: "list", name: "algorithm", message: "Algorithm:", choices: ["sha256", "md5"] }]);
3446
+ fns.push({ fn, algorithm });
3447
+ } else if (fn === "regex_extract") {
3448
+ const a = await inquirer2.prompt([
3449
+ { type: "input", name: "pattern", message: "Regex pattern:", validate: (s) => {
3450
+ try {
3451
+ new RegExp(s);
3452
+ return true;
3453
+ } catch {
3454
+ return "invalid regex";
3455
+ }
3456
+ } },
3457
+ { type: "input", name: "group", message: "Capture group (default 1):", default: "1" }
3458
+ ]);
3459
+ fns.push({ fn, pattern: a.pattern, ...Number(a.group) !== 1 ? { group: Number(a.group) } : {} });
3460
+ } else if (fn === "concat") {
3461
+ const a = await inquirer2.prompt([
3462
+ { type: "input", name: "before", message: "Text before (empty = none):" },
3463
+ { type: "input", name: "after", message: "Text after (empty = none):" }
3464
+ ]);
3465
+ fns.push({ fn, ...a.before ? { before: a.before } : {}, ...a.after ? { after: a.after } : {} });
3466
+ } else {
3467
+ fns.push({ fn });
3468
+ }
3469
+ }
3470
+ }
3471
+ async function buildCondition(phase) {
3472
+ const { default: inquirer2 } = await import("inquirer");
3473
+ const { want } = await inquirer2.prompt([{
3474
+ type: "confirm",
3475
+ name: "want",
3476
+ default: false,
3477
+ message: "Only apply when a condition matches?"
3478
+ }]);
3479
+ if (!want) return void 0;
3480
+ const srcHint = phase === "response" ? "(header:x-foo, bodyvar:user.id, status:)" : "(header:x-foo, param:limit, bodyvar:user.id, jwt:sub)";
3481
+ const items = [];
3482
+ for (; ; ) {
3483
+ const a = await inquirer2.prompt([
3484
+ { type: "input", name: "source", message: `Condition field ${import_chalk26.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
3485
+ { type: "list", name: "operator", message: "Operator:", choices: [
3486
+ "eq",
3487
+ "neq",
3488
+ "contains",
3489
+ "starts_with",
3490
+ "ends_with",
3491
+ "exists",
3492
+ "not_exists",
3493
+ "regex"
3494
+ ] }
3495
+ ]);
3496
+ let value;
3497
+ if (a.operator !== "exists" && a.operator !== "not_exists") {
3498
+ const v = await inquirer2.prompt([{ type: "input", name: "value", message: a.operator === "regex" ? "Pattern:" : "Value:" }]);
3499
+ value = v.value;
3500
+ }
3501
+ items.push({ id: `c${items.length + 1}`, openParen: false, closeParen: false, source: a.source, operator: a.operator, ...value !== void 0 ? { value } : {} });
3502
+ const { more } = await inquirer2.prompt([{
3503
+ type: "list",
3504
+ name: "more",
3505
+ message: "Combine with another condition?",
3506
+ choices: [{ name: "\u2713 Done", value: null }, { name: "AND \u2026", value: "AND" }, { name: "OR \u2026", value: "OR" }]
3507
+ }]);
3508
+ if (!more) return items;
3509
+ items[items.length - 1].logicOp = more;
3510
+ }
3511
+ }
3512
+ function showCondition(cond) {
3513
+ if (!Array.isArray(cond) || !cond.length) return "";
3514
+ const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
3515
+ return import_chalk26.default.dim(` when ${s}`);
3516
+ }
3421
3517
  async function transformsMenu(proj2) {
3422
3518
  const { default: inquirer2 } = await import("inquirer");
3423
3519
  const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
@@ -3428,8 +3524,9 @@ async function transformsMenu(proj2) {
3428
3524
  if (!rules.length) console.log(import_chalk26.default.dim(" No transform rules yet."));
3429
3525
  for (const r of rules) {
3430
3526
  const a = r.action ?? {};
3431
- const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}`;
3432
- console.log(` ${r.enabled ? import_chalk26.default.green("\u25CF") : import_chalk26.default.dim("\u25CB")} ${import_chalk26.default.bold(r.name)} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)} ${what}`);
3527
+ const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
3528
+ const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ? import_chalk26.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
3529
+ console.log(` ${r.enabled ? import_chalk26.default.green("\u25CF") : import_chalk26.default.dim("\u25CB")} ${import_chalk26.default.bold(r.name)} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
3433
3530
  }
3434
3531
  const { act } = await inquirer2.prompt([{
3435
3532
  type: "list",
@@ -3441,10 +3538,26 @@ async function transformsMenu(proj2) {
3441
3538
  { name: "Enable/disable a rule", value: "toggle" },
3442
3539
  { name: "Delete a rule", value: "delete" }
3443
3540
  ] : [],
3541
+ { name: import_chalk26.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
3444
3542
  { name: "\u2190 Back", value: "back" }
3445
3543
  ]
3446
3544
  }]);
3447
3545
  if (act === "back") return;
3546
+ if (act === "raw") {
3547
+ const { raw } = await inquirer2.prompt([{
3548
+ type: "input",
3549
+ name: "raw",
3550
+ message: "Rule JSON ({name, phase, enabled, action, condition?}):"
3551
+ }]);
3552
+ const body = parseValue(raw);
3553
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
3554
+ console.log(import_chalk26.default.yellow(" Not a JSON object \u2014 skipped."));
3555
+ continue;
3556
+ }
3557
+ await admin({ method: "POST", path: base, body, summary: "Create transform rule (raw JSON)" });
3558
+ console.log(import_chalk26.default.green(" Rule created."));
3559
+ continue;
3560
+ }
3448
3561
  if (act === "add") {
3449
3562
  const ans = await inquirer2.prompt([
3450
3563
  { type: "input", name: "name", message: "Rule name:", validate: (s) => !!s || "required" },
@@ -3477,11 +3590,26 @@ async function transformsMenu(proj2) {
3477
3590
  { type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
3478
3591
  { type: "confirm", name: "strip", message: "Remove the source field after copying?", default: false }
3479
3592
  ]);
3480
- action2 = { type: "copy", source: a.source, destination: a.destination, ...a.strip ? { strip_source: true } : {} };
3593
+ const source_fns = await buildFns("Transform the value as it is READ (source functions)");
3594
+ const dest_fns = await buildFns("Transform the value as it is WRITTEN (destination functions)");
3595
+ action2 = {
3596
+ type: "copy",
3597
+ source: a.source,
3598
+ destination: a.destination,
3599
+ ...a.strip ? { strip_source: true } : {},
3600
+ ...source_fns.length ? { source_fns } : {},
3601
+ ...dest_fns.length ? { dest_fns } : {}
3602
+ };
3481
3603
  }
3604
+ const condition = await buildCondition(ans.phase);
3482
3605
  const spinner = (0, import_ora11.default)("Creating rule...").start();
3483
3606
  try {
3484
- await admin({ method: "POST", path: base, body: { name: ans.name, phase: ans.phase, enabled: true, action: action2 }, summary: `Create transform "${ans.name}"` });
3607
+ await admin({
3608
+ method: "POST",
3609
+ path: base,
3610
+ body: { name: ans.name, phase: ans.phase, enabled: true, action: action2, ...condition ? { condition } : {} },
3611
+ summary: `Create transform "${ans.name}"`
3612
+ });
3485
3613
  spinner.succeed(`Rule "${ans.name}" created.`);
3486
3614
  } catch (err) {
3487
3615
  spinner.fail("Create failed.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "APIblaze CLI + sidecar — manage API proxies, run dev tunnels, and route a Next.js app's egress through APIblaze with one command",
5
5
  "keywords": [
6
6
  "apiblaze",