greybull 0.2.2 → 0.2.4
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 +49 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ var ApiError = class extends Error {
|
|
|
94
94
|
this.status = status;
|
|
95
95
|
}
|
|
96
96
|
};
|
|
97
|
-
var VERSION = "0.2.
|
|
97
|
+
var VERSION = "0.2.4";
|
|
98
98
|
function buildRequest(opts) {
|
|
99
99
|
const url = new URL(opts.apiUrl.replace(/\/+$/, "") + opts.path);
|
|
100
100
|
if (opts.query) {
|
|
@@ -148,6 +148,17 @@ async function apiRequest(opts) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// src/output.ts
|
|
151
|
+
var COLOR = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
152
|
+
function paint(code, s) {
|
|
153
|
+
return COLOR ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
154
|
+
}
|
|
155
|
+
var color = {
|
|
156
|
+
red: (s) => paint("31", s),
|
|
157
|
+
green: (s) => paint("32", s),
|
|
158
|
+
yellow: (s) => paint("33", s),
|
|
159
|
+
bold: (s) => paint("1", s),
|
|
160
|
+
dim: (s) => paint("2", s)
|
|
161
|
+
};
|
|
151
162
|
function formatTable(headers, rows, opts = {}) {
|
|
152
163
|
if (rows.length === 0) return "(none)";
|
|
153
164
|
const max = opts.max ?? [];
|
|
@@ -175,7 +186,7 @@ Run 'greybull login' to authenticate (or set GREYBULL_API_KEY).`;
|
|
|
175
186
|
Your API key is missing a required scope \u2014 create one with DNS/domain permissions at ${opts.apiUrl}/api-settings.`;
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
|
-
process.stderr.write(
|
|
189
|
+
process.stderr.write(`${color.red(color.bold("Error:"))} ${color.red(message)}
|
|
179
190
|
`);
|
|
180
191
|
process.exit(1);
|
|
181
192
|
}
|
|
@@ -478,7 +489,7 @@ function renderZoneFile(zone, records) {
|
|
|
478
489
|
"; -*- mode: dns -*-",
|
|
479
490
|
`; Zone: ${zone}`,
|
|
480
491
|
"; Edit records below, then save and close your editor to apply.",
|
|
481
|
-
"; Format: NAME TTL TYPE CONTENT (
|
|
492
|
+
"; Format: NAME [TTL] TYPE CONTENT (@ = apex; TTL optional, default 3600)",
|
|
482
493
|
"; TXT values keep their quotes; MX/SRV keep priority in CONTENT.",
|
|
483
494
|
"; SOA and the apex NS records are managed for you and not shown.",
|
|
484
495
|
";"
|
|
@@ -494,12 +505,17 @@ function parseZoneFile(text) {
|
|
|
494
505
|
for (let i = 0; i < lines.length; i++) {
|
|
495
506
|
const trimmed = lines[i].trim();
|
|
496
507
|
if (!trimmed || trimmed.startsWith(";")) continue;
|
|
497
|
-
const m = trimmed.match(/^(\S+)\s+(\d+)\s+(?:IN\s+)?([A-Za-z][A-Za-z0-9]*)\s+(.+)$/i);
|
|
508
|
+
const m = trimmed.match(/^(\S+)\s+(?:(\d+)\s+)?(?:IN\s+)?([A-Za-z][A-Za-z0-9]*)\s+(.+)$/i);
|
|
498
509
|
if (!m) {
|
|
499
|
-
throw new Error(`line ${i + 1}: could not parse "${trimmed}" (expected: NAME TTL TYPE CONTENT)`);
|
|
510
|
+
throw new Error(`line ${i + 1}: could not parse "${trimmed}" (expected: NAME [TTL] TYPE CONTENT)`);
|
|
500
511
|
}
|
|
501
512
|
const [, name, ttlStr, typeRaw, content] = m;
|
|
502
|
-
recs.push({
|
|
513
|
+
recs.push({
|
|
514
|
+
name,
|
|
515
|
+
type: typeRaw.toUpperCase(),
|
|
516
|
+
content: content.trim(),
|
|
517
|
+
ttl: ttlStr ? parseInt(ttlStr) : 3600
|
|
518
|
+
});
|
|
503
519
|
}
|
|
504
520
|
return recs;
|
|
505
521
|
}
|
|
@@ -527,11 +543,21 @@ async function prompt(question, valid, def, nonTtyDefault = def) {
|
|
|
527
543
|
rl.close();
|
|
528
544
|
}
|
|
529
545
|
}
|
|
530
|
-
function
|
|
546
|
+
function isVimFamily(cmd) {
|
|
531
547
|
const base = path2.basename(cmd).replace(/\.exe$/i, "").toLowerCase();
|
|
532
|
-
if (["vim", "nvim", "gvim", "mvim"].includes(base))
|
|
533
|
-
|
|
548
|
+
if (["vim", "nvim", "gvim", "mvim", "vimdiff", "view"].includes(base)) return true;
|
|
549
|
+
if (base === "vi") {
|
|
550
|
+
try {
|
|
551
|
+
const r = spawnSync(cmd, ["--version"], { encoding: "utf8", timeout: 2e3 });
|
|
552
|
+
return /\bn?vim\b/i.test(`${r.stdout ?? ""}${r.stderr ?? ""}`);
|
|
553
|
+
} catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
534
556
|
}
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
function editorArgv(args, file, forceVim) {
|
|
560
|
+
if (forceVim) return [...args, "-c", "syntax enable", "-c", "set ft=bindzone", file];
|
|
535
561
|
return [...args, file];
|
|
536
562
|
}
|
|
537
563
|
function openInEditor(zone, content) {
|
|
@@ -540,7 +566,7 @@ function openInEditor(zone, content) {
|
|
|
540
566
|
const editor = process.env.VISUAL || process.env.EDITOR || (process.platform === "win32" ? "notepad" : "vi");
|
|
541
567
|
const [cmd, ...args] = editor.split(" ");
|
|
542
568
|
try {
|
|
543
|
-
const r = spawnSync(cmd, editorArgv(
|
|
569
|
+
const r = spawnSync(cmd, editorArgv(args, file, isVimFamily(cmd)), { stdio: "inherit" });
|
|
544
570
|
if (r.error) return null;
|
|
545
571
|
return fs2.readFileSync(file, "utf8");
|
|
546
572
|
} finally {
|
|
@@ -567,9 +593,9 @@ async function editCmd(ctx2, zone) {
|
|
|
567
593
|
try {
|
|
568
594
|
records = parseZoneFile(edited);
|
|
569
595
|
} catch (err) {
|
|
570
|
-
process.stdout.write(
|
|
571
|
-
Error parsing the zone:
|
|
572
|
-
|
|
596
|
+
process.stdout.write(
|
|
597
|
+
"\n" + color.red(color.bold("Error parsing the zone: ") + err.message) + "\n"
|
|
598
|
+
);
|
|
573
599
|
const c2 = await prompt(
|
|
574
600
|
"Options are: (e)dit your changes, (r)etry with original zone, (a)bort? [e] ",
|
|
575
601
|
["e", "r", "a"],
|
|
@@ -584,11 +610,9 @@ Error parsing the zone: ${err.message}
|
|
|
584
610
|
if (added.length === 0 && removed.length === 0) {
|
|
585
611
|
return void process.stdout.write("No changes.\n");
|
|
586
612
|
}
|
|
587
|
-
process.stdout.write("\
|
|
588
|
-
removed.forEach((l) => process.stdout.write(` - ${l}
|
|
589
|
-
`));
|
|
590
|
-
added.forEach((l) => process.stdout.write(` + ${l}
|
|
591
|
-
`));
|
|
613
|
+
process.stdout.write("\n" + color.bold("Changes to apply:") + "\n");
|
|
614
|
+
removed.forEach((l) => process.stdout.write(color.red(` - ${l}`) + "\n"));
|
|
615
|
+
added.forEach((l) => process.stdout.write(color.green(` + ${l}`) + "\n"));
|
|
592
616
|
process.stdout.write("\n");
|
|
593
617
|
const c = await prompt(
|
|
594
618
|
"(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit? [a] ",
|
|
@@ -612,14 +636,15 @@ Error parsing the zone: ${err.message}
|
|
|
612
636
|
path: `/api/v1/dns/zones/${encodeURIComponent(zone)}`,
|
|
613
637
|
body: { records }
|
|
614
638
|
});
|
|
615
|
-
process.stdout.write(
|
|
616
|
-
`)
|
|
639
|
+
process.stdout.write(
|
|
640
|
+
color.green(`Applied. ${zone} now has ${put.data.applied} record(s).`) + "\n"
|
|
641
|
+
);
|
|
617
642
|
return;
|
|
618
643
|
} catch (err) {
|
|
619
644
|
if (err instanceof ApiError && (err.status === 422 || err.status === 400)) {
|
|
620
|
-
process.stdout.write(
|
|
621
|
-
The DNS server rejected the zone:
|
|
622
|
-
|
|
645
|
+
process.stdout.write(
|
|
646
|
+
"\n" + color.red(color.bold("The DNS server rejected the zone: ") + err.message) + "\n"
|
|
647
|
+
);
|
|
623
648
|
const c2 = await prompt(
|
|
624
649
|
"Options are: (e)dit your changes, (r)etry with original zone, (a)bort? [e] ",
|
|
625
650
|
["e", "r", "a"],
|
|
@@ -674,7 +699,7 @@ async function domainsCheckCmd(ctx2, query, opts) {
|
|
|
674
699
|
|
|
675
700
|
// src/index.ts
|
|
676
701
|
var program = new Command();
|
|
677
|
-
program.name("greybull").description("Manage Greybull DNS records and domains from the command line").version("0.2.
|
|
702
|
+
program.name("greybull").description("Manage Greybull DNS records and domains from the command line").version("0.2.4").option("--json", "output raw JSON", false).option("--api-url <url>", "portal base URL (or set GREYBULL_API_URL)");
|
|
678
703
|
function ctx(requireAuth = true) {
|
|
679
704
|
const opts = program.opts();
|
|
680
705
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|