appwarden
Version:
CLI tool to manage Appwarden monitors and deployments
818 lines (793 loc) • 28.4 kB
JavaScript
// src/index.ts
import { Agent, setGlobalDispatcher } from "undici";
// src/runner/index.ts
import { Command, Help } from "commander";
// src/tasks/domains-view/domains-view.ts
import fs from "fs";
import YAML from "yaml";
// src/constants.ts
var appwardenDir = "./.appwarden";
var domainsDir = `${appwardenDir}/domains`;
var workerDir = `${appwardenDir}/cloudflare-worker`;
function splitOnSerialNumber(str) {
const index = str.search(/\d{4}/);
if (index === -1) {
return [str, ""];
}
const before = str.substring(0, index);
const after = str.substring(index);
return [before, after];
}
var unquote = (str) => (
// replace the opening and closing quotation in str if they exist
str.replace(/^"|"$/g, "")
);
var allowedDNSRecords = [
{ type: "A" },
{ type: "AAAA" },
{ type: "CNAME" },
{ type: "TXT", transformer: unquote },
{ type: "MX" },
{
type: "NS",
transformer: (value) => value.endsWith(".") ? value : `${value}.`
},
{
type: "SOA",
transformer: (value) => {
const [prefix] = splitOnSerialNumber(value);
return prefix.trim();
},
validator: (value) => {
const [, soaNumbers] = splitOnSerialNumber(value);
if (soaNumbers) {
return "starts-with";
}
return void 0;
}
},
{ type: "SRV" },
{ type: "CAA" },
{ type: "DS" },
{ type: "DNSKEY" },
{ type: "RRSIG" },
{ type: "NSEC" },
{ type: "NSEC3" },
{ type: "NSEC3PARAM" },
{ type: "SPF" },
{ type: "DKIM" },
{ type: "DMARC" }
];
var vercelCIDRBlocks = [
{
comment: "CIDR range for https://dnschecker.org/ip-whois-lookup.php?query=76.76.21.0",
content: "76.76.21.0/24"
},
{
comment: "CIDR range for https://dnschecker.org/ip-whois-lookup.php?query=216.198.79.0",
content: "216.198.79.0/24"
},
{
comment: "CIDR range for https://dnschecker.org/ip-whois-lookup.php?query=66.33.60.0",
content: "66.33.60.0/24"
},
{
comment: "CIDR range for https://myip.ms/view/ip_owners/1127467/Vercel_Inc.html",
content: "216.230.84.0/24"
},
{
comment: "CIDR range for https://myip.ms/view/ip_owners/1127467/Vercel_Inc.html",
content: "198.169.2.0/24"
},
{
comment: "CIDR range for https://myip.ms/view/ip_owners/1127467/Vercel_Inc.html",
content: "64.29.17.0/24"
},
{
comment: "CIDR range for https://myip.ms/view/ip_owners/1127467/Vercel_Inc.html",
content: "198.169.1.0/24"
},
{
comment: "CIDR range for https://myip.ms/view/ip_owners/1127467/Vercel_Inc.html",
content: "64.239.109.0/24"
}
];
var allowedHeaderNames = [
"content-type",
// Note: transfer-encoding is excluded because it's not stable across CDNs/servers
// (servers dynamically choose between transfer-encoding: chunked vs content-length)
"connection",
"cache-control",
"vary",
"server",
// security headers
"content-security-policy",
"x-content-type-options",
"x-frame-options",
"strict-transport-security",
"x-xss-protection",
"referrer-policy",
"feature-policy",
"permissions-policy",
"cross-origin-embedder-policy",
"cross-origin-opener-policy",
"cross-origin-resource-policy",
"access-control-allow-origin",
"access-control-allow-methods",
"access-control-allow-headers",
"access-control-expose-headers",
"access-control-allow-credentials",
"access-control-max-age",
"public-key-pins",
"expect-ct"
];
// src/tasks/domains-view/domains-view.ts
var readDomainConfig = (filePath) => {
try {
const yamlContent = fs.readFileSync(filePath, "utf-8");
return YAML.parse(yamlContent);
} catch {
return null;
}
};
var findMiddleware = (middleware, url) => middleware?.find((m) => m.url === url);
var buildWebsiteView = (monitor, middleware) => ({
url: monitor.url,
recordCount: monitor.headers?.length ?? 0,
middlewareConfigured: !!middleware,
cspConfigured: !!middleware?.options?.["csp-mode"] || !!middleware?.options?.["csp-directives"],
lockPageSlug: middleware?.options?.["lock-page-slug"]
});
var buildDomainView = (config) => {
const middleware = config.services?.middleware ?? [];
const monitors = config.services?.monitors ?? [];
return {
hostname: config.hostname,
dnsRecordCount: config.dns?.monitors?.length ?? 0,
services: monitors.map(
(monitor) => buildWebsiteView(monitor, findMiddleware(middleware, monitor.url))
)
};
};
var getDomainViews = (hostname) => {
if (!fs.existsSync(domainsDir)) {
return [];
}
const files = fs.readdirSync(domainsDir);
const views = [];
for (const file of files) {
if (!file.endsWith(".yml")) continue;
const config = readDomainConfig(`${domainsDir}/${file}`);
if (!config) continue;
if (hostname && config.hostname !== hostname) continue;
views.push(buildDomainView(config));
}
return views;
};
var buildInitCommand = (views) => {
const domains = /* @__PURE__ */ new Set();
for (const view of views) {
domains.add(view.hostname);
for (const website of view.services) {
domains.add(website.url);
}
}
const args = Array.from(domains).filter((d) => d !== "example.com").map((d) => `-d ${d}`).join(" ");
return `appwarden sync ${args}`;
};
// src/commands/domains-view/domains-view.ts
var printTree = (views) => {
for (let i = 0; i < views.length; i++) {
const view = views[i];
const isLastDomain = i === views.length - 1;
const domainPrefix = isLastDomain ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
const domainIndent = isLastDomain ? " " : "\u2502 ";
console.log(`${domainPrefix} ${view.hostname}`);
console.log(`${domainIndent}\u2514\u2500\u2500 DNS records: ${view.dnsRecordCount}`);
for (let j = 0; j < view.services.length; j++) {
const website = view.services[j];
const isLastWebsite = j === view.services.length - 1;
const websitePrefix = isLastWebsite ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
const websiteIndent = isLastWebsite ? " " : "\u2502 ";
const websiteConnector = " ";
console.log(`${domainIndent}${websiteConnector}${websitePrefix} ${website.url}`);
console.log(
`${domainIndent}${websiteConnector}${websiteIndent}\u251C\u2500\u2500 Records: ${website.recordCount}`
);
console.log(
`${domainIndent}${websiteConnector}${websiteIndent}\u2514\u2500\u2500 Middleware: ${website.middlewareConfigured ? "yes" : "no"}`
);
if (website.middlewareConfigured) {
console.log(
`${domainIndent}${websiteConnector}${websiteIndent} \u251C\u2500\u2500 CSP: ${website.cspConfigured ? "yes" : "no"}`
);
console.log(
`${domainIndent}${websiteConnector}${websiteIndent} \u2514\u2500\u2500 Lock page: ${website.lockPageSlug ?? "none"}`
);
}
}
}
};
var domainsViewCommand = {
command: "domains view [hostname]",
description: "View domain configuration files in .appwarden/domains",
options: ["--json", "Output the result as JSON"],
action: async (args) => {
const views = getDomainViews(args.hostname);
if (views.length === 0) {
console.log("[@appwarden] No domain configurations found in .appwarden/domains");
return;
}
if (args.json) {
console.log(JSON.stringify(views, null, 2));
} else {
printTree(views);
}
console.log("");
console.log(
`[@appwarden] This a summary of your domain configuration files in this repository. To view your deployed configuration, visit the Appwarden [dashboard](https://use.appwarden.io)`
);
console.log(
`[@appwarden] Run the following command to sync your domain configurations with detected records:
${buildInitCommand(views)}
`
);
}
};
// src/commands/sync/sync.ts
import fs2 from "fs";
import YAML3 from "yaml";
// src/utils/debug.ts
var debugEnabled = false;
var setDebug = (enabled) => {
debugEnabled = enabled;
};
var debug = (...msg) => {
if (debugEnabled) {
console.log(...msg);
}
};
// src/utils/ensure-protocol.ts
var protocolRegex = /^https?:\/\//i;
var ensureProtocol = (maybeFQDN) => {
const hasProtocol = protocolRegex.test(maybeFQDN);
if (!hasProtocol) {
return `https://${maybeFQDN}`;
}
return maybeFQDN;
};
// src/utils/get-dns-records.ts
import { getAllDnsRecords } from "@layered/dns-records";
// src/utils/parse-domain.ts
import { ParseResultType, fromUrl, parseDomain } from "parse-domain";
var parseHostname = (hostname) => parseDomain(fromUrl(hostname));
var getDomainMetadata = (thing) => {
const parseResult = parseHostname(thing);
if (parseResult.type === ParseResultType.Listed && parseResult.domain) {
return parseResult;
}
return false;
};
var getRootDomain = (listed) => listed.domain + "." + listed.topLevelDomains.join(".");
var transformLayeredDNSRecords = (recordsList) => sortDNSRecords(
recordsList.map((record) => ({
name: record.name,
type: record.type,
content: record.data
}))
);
var sortDNSRecords = (records) => records.sort((a, b) => {
if (a.type && b.type) {
if (a.type < b.type) {
return -1;
}
if (a.type > b.type) {
return 1;
}
}
if (a.name && b.name) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
}
if (a.content && b.content) {
if (a.content < b.content) {
return -1;
}
if (a.content > b.content) {
return 1;
}
}
return 0;
});
// src/utils/get-dns-records.ts
var getDNSRecords = (domain) => getAllDnsRecords(domain, {
resolver: "cloudflare-dns",
commonSubdomainsCheck: false,
subdomains: []
}).then((records) => transformLayeredDNSRecords(records));
// src/utils/is-cloudflare.ts
var isCloudflareResponse = (response) => {
const server = response.headers.get("server");
const cfRay = response.headers.get("cf-ray");
return server?.toLowerCase() === "cloudflare" || cfRay !== null && cfRay.length > 0;
};
// src/utils/normalize-csp.ts
var normalizeCsp = (headerValue) => {
return headerValue.replaceAll(/'nonce-[^']+'/g, "{{nonce}}");
};
// src/utils/stringify-yaml-with-comments.ts
import YAML2 from "yaml";
function stringifyYAMLWithComments(content) {
const cleanContent = removeCommentProperties(content);
const doc = YAML2.parseDocument(YAML2.stringify(cleanContent));
if (doc.contents && YAML2.isMap(doc.contents)) {
const dnsNode = doc.contents.get("dns");
if (dnsNode && YAML2.isMap(dnsNode)) {
const monitorsNode = dnsNode.get("monitors");
if (monitorsNode && YAML2.isSeq(monitorsNode)) {
monitorsNode.items.forEach((monitorItem, monitorIndex) => {
if (!YAML2.isMap(monitorItem)) return;
const originalMonitor = content.dns?.monitors?.[monitorIndex];
if (!originalMonitor) return;
if (originalMonitor.comment && YAML2.isNode(monitorItem)) {
monitorItem.commentBefore = ` ${originalMonitor.comment}`;
}
const contentNode = monitorItem.get("content");
if (contentNode && YAML2.isSeq(contentNode) && Array.isArray(originalMonitor.content)) {
contentNode.items.forEach((contentItem, contentIndex) => {
const originalContent = originalMonitor.content[contentIndex];
if (originalContent && typeof originalContent === "object" && originalContent.comment && YAML2.isNode(contentItem)) {
contentItem.commentBefore = ` ${originalContent.comment}`;
}
});
}
});
}
}
}
return doc.toString();
}
function removeCommentProperties(obj) {
if (Array.isArray(obj)) {
return obj.map((item) => removeCommentProperties(item));
} else if (obj && typeof obj === "object") {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (key !== "comment") {
result[key] = removeCommentProperties(value);
}
}
return result;
}
return obj;
}
// src/utils/is-vercel-ip.ts
import ipaddr from "ipaddr.js";
var isIpInCIDRBlock = (ip, cidr) => {
try {
const addr = ipaddr.parse(ip);
const range = ipaddr.parseCIDR(cidr);
return addr.kind() === range[0].kind() && addr.match(range);
} catch (_error) {
return false;
}
};
var isVercelIP = (ip) => {
return vercelCIDRBlocks.some((block) => isIpInCIDRBlock(ip, block.content));
};
var hasVercelIPs = (ips) => {
return ips.some((ip) => isVercelIP(ip));
};
// src/utils/sleep.ts
var sleep = (timeout = 1e3) => new Promise((resolve) => setTimeout(resolve, timeout));
// src/commands/sync/sync.ts
var YAML_LANGUAGE_SERVER_SCHEMA_COMMENT = "# yaml-language-server: $schema=../../node_modules/appwarden/build/domain-configuration-file.json";
var readExistingConfig = (domain) => {
const filePath = `${domainsDir}/${domain.replaceAll(".", "-")}.yml`;
if (!fs2.existsSync(filePath)) {
return null;
}
try {
const yamlContent = fs2.readFileSync(filePath, "utf-8");
const parsed = YAML3.parse(yamlContent);
return parsed;
} catch (error) {
console.warn(`[@appwarden] Warning: Could not parse existing config for ${domain}:`, error);
return null;
}
};
var logFinishedScaffolding = (domains) => {
console.log(
`[@appwarden] Created an .appwarden folder in your current directory with ${domains.length} ${domains.length === 1 ? "domain" : "domains"}...`
);
if (domains.length === 0) {
console.log(
"[@appwarden] To monitor a domain with Appwarden, run `npx appwarden sync -d example.com`."
);
}
const exampleFilePath = `${domainsDir}/example-com.yml`;
const exampleFileExisted = fs2.existsSync(exampleFilePath);
if (exampleFileExisted) {
fs2.unlinkSync(exampleFilePath);
}
console.log(`[@appwarden] .appwarden/`);
console.log(`[@appwarden] \u2514\u2500\u2500 domains/`);
if (fs2.existsSync(".appwarden/domains")) {
fs2.readdirSync(".appwarden/domains").forEach((file, i, list) => {
const isLast = i === list.length - 1;
const prefix = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
console.log(`[@appwarden] ${prefix} ${file}`);
});
}
if (exampleFileExisted) {
console.log(
"[@appwarden] Deleted `.appwarden/domains/example-com.yml` because it is only used for tutorial purposes and is not needed for production use."
);
}
console.log(`[@appwarden]`);
};
var syncCommand = {
command: "sync",
description: "Scaffold .appwarden directories and sync domain configuration files with live DNS records and website headers for the provided domains. Extends existing configurations without overwriting them.",
options: [
"-d, --domain <domains...>",
"Create a domain configuration file for this domain (e.g. appwarden.io)",
"--vercel",
"Manually enable Vercel-specific DNS monitoring with CIDR block validation (auto-detected by default)"
],
action: async (args) => {
console.log("[@appwarden] Running `sync` command...");
const domains = args.domain ?? [];
const domainsMap = {};
for (const domain of domains) {
const domainMetadata = getDomainMetadata(domain);
if (!domainMetadata) {
console.error(`[@appwarden] Invalid domain: ${domain}`);
throw new Error(`Invalid domain: ${domain}`);
}
const rootDomain = getRootDomain(domainMetadata);
if (rootDomain) {
domainsMap[rootDomain] = [...domainsMap[rootDomain] ?? [], domainMetadata];
}
}
try {
for (const dir of [appwardenDir, domainsDir]) {
fs2.mkdirSync(dir, { recursive: true });
}
} catch (_error) {
}
if (Object.keys(domainsMap).length === 0) {
return logFinishedScaffolding(domains);
}
for (const [domain, subdomains] of Object.entries(domainsMap)) {
const existingConfig = readExistingConfig(domain);
const subdomainsBeingProcessed = new Set(subdomains.map((s) => s.hostname));
let monitorFileContent = {
hostname: domain,
version: 1,
dns: {
options: existingConfig?.dns?.options ?? {
links: []
},
monitors: existingConfig?.dns?.monitors ?? []
},
// Ensure services.options appears first under `services` in the generated YAML
services: existingConfig?.services ?? {
options: {
links: []
},
monitors: [],
middleware: []
}
};
let dnsRecords = [];
try {
const subdomainsList = subdomains.map((subdomain) => subdomain.hostname).flat();
const promises = [];
for (const subdomain of subdomainsList) {
promises.push(getDNSRecords(subdomain));
}
dnsRecords = (await Promise.all(promises)).flat();
} catch (error) {
if (error instanceof Error) {
console.error(`[@appwarden] Error fetching DNS records for ${domain}:`, error.message);
}
}
debug(`Detected DNS Records:`, dnsRecords);
let isFirstRecord = true;
const monitoredRecords = [];
const detectedARecords = dnsRecords.filter((record) => record.type === "A");
const detectedARecordIPs = detectedARecords.map((record) => record.content);
const isVercelDetected = hasVercelIPs(detectedARecordIPs);
const shouldUseVercelCIDR = args.vercel || isVercelDetected;
if (shouldUseVercelCIDR) {
if (isVercelDetected && !args.vercel) {
console.log(
"[@appwarden] Detected Vercel infrastructure - automatically enabling CIDR block validation for dynamic IPs..."
);
} else if (args.vercel) {
console.log(
"[@appwarden] Adding Vercel-specific dynamic IP monitoring with CIDR block validation..."
);
}
monitoredRecords.push({
name: domain,
type: "A",
validator: "cidr-block",
comment: "Vercel uses dynamic IP addresses for traffic optimization.",
content: vercelCIDRBlocks
});
}
for (const dnsRecord of dnsRecords) {
for (const allowedRecord of allowedDNSRecords) {
if (shouldUseVercelCIDR && dnsRecord.type === "A") {
continue;
}
if (dnsRecord.type === "TXT" && typeof dnsRecord.content === "string" && dnsRecord.content.startsWith("appwarden-domain-verification")) {
continue;
}
if (dnsRecord.type !== allowedRecord.type) {
continue;
}
const existingRecord = monitoredRecords.find(
(record) => record.name === dnsRecord.name && record.type === dnsRecord.type
);
const makeDNSEntry = (dnsRecord2, allowedRecord2, isFirstRecord2) => ({
content: allowedRecord2.transformer ? allowedRecord2.transformer(dnsRecord2.content) : dnsRecord2.content,
...allowedRecord2.validator && {
validator: allowedRecord2.validator(dnsRecord2.content)
},
...isFirstRecord2 && {
comment: "This is the first nested record"
}
});
if (existingRecord) {
if (typeof existingRecord.content === "string") {
existingRecord.content = [
makeDNSEntry(existingRecord, allowedRecord, isFirstRecord),
makeDNSEntry(dnsRecord, allowedRecord)
];
isFirstRecord = false;
} else {
existingRecord.content.push(makeDNSEntry(dnsRecord, allowedRecord));
}
} else {
monitoredRecords.push({
name: dnsRecord.name,
type: dnsRecord.type,
...makeDNSEntry(dnsRecord, allowedRecord)
});
}
}
}
debug("Monitoring DNS Records:", JSON.stringify(monitoredRecords, null, 2));
const existingDNSMonitors = existingConfig?.dns?.monitors ?? [];
const preservedDNSMonitors = existingDNSMonitors.filter(
(monitor) => !subdomainsBeingProcessed.has(monitor.name)
);
monitorFileContent.dns.monitors = [...preservedDNSMonitors, ...monitoredRecords];
const newWebsiteMonitors = [];
const cloudflareHostedUrls = /* @__PURE__ */ new Set();
for (const subdomain of subdomains) {
const subdomainWebsiteMonitor = {
url: subdomain.hostname,
status: 200,
headers: []
};
let websiteResponse = null;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 1e4);
try {
websiteResponse = await fetch(new URL(ensureProtocol(subdomain.hostname)), {
headers: { "content-type": "text/html; charset=utf-8" },
signal: controller.signal
});
} catch (error) {
if (error instanceof Error) {
console.log(
`[@appwarden] Error fetching website headers from ${subdomain.hostname}:`,
error.message
);
}
} finally {
clearTimeout(timeoutId);
}
const websiteHeaders = [];
if (websiteResponse) {
for (const headerName of allowedHeaderNames) {
let headerValue = websiteResponse.headers.get(headerName);
if (headerValue) {
if (headerName === "content-security-policy") {
headerValue = normalizeCsp(headerValue);
}
websiteHeaders.push({ name: headerName, value: headerValue });
}
}
if (isCloudflareResponse(websiteResponse)) {
cloudflareHostedUrls.add(subdomain.hostname);
}
}
subdomainWebsiteMonitor.headers = websiteHeaders;
newWebsiteMonitors.push(subdomainWebsiteMonitor);
}
const existingWebsiteMonitors = existingConfig?.services?.monitors ?? [];
const preservedWebsiteMonitors = existingWebsiteMonitors.filter(
(monitor) => !subdomainsBeingProcessed.has(monitor.url)
);
const existingMiddleware = existingConfig?.services?.middleware ?? [];
const existingMiddlewareUrls = new Set(existingMiddleware.map((entry) => entry.url));
const mergedMiddleware = [...existingMiddleware];
for (const monitor of newWebsiteMonitors) {
if (existingMiddlewareUrls.has(monitor.url)) {
continue;
}
const isCloudflare = cloudflareHostedUrls.has(monitor.url);
const scriptSrc = ["self"];
if (isCloudflare) {
scriptSrc.push("{{nonce}}");
}
mergedMiddleware.push({
url: monitor.url,
options: {
"lock-page-slug": "/maintenance",
"csp-mode": "report-only",
"csp-directives": {
"script-src": scriptSrc
}
}
});
}
monitorFileContent.services = {
options: existingConfig?.services?.options ?? { links: [] },
monitors: [...preservedWebsiteMonitors, ...newWebsiteMonitors],
middleware: mergedMiddleware
};
const yamlBody = stringifyYAMLWithComments(monitorFileContent);
const yamlContent = `${YAML_LANGUAGE_SERVER_SCHEMA_COMMENT}
${yamlBody}
`;
debug(`${domainsDir}/${domain.replaceAll(".", "-")}.yml
`, `${yamlContent}
`);
fs2.writeFileSync(`${domainsDir}/${domain.replaceAll(".", "-")}.yml`, yamlContent);
}
logFinishedScaffolding(domains);
await sleep();
console.log(
`[@appwarden] To finish setting up Appwarden, return to the [dashboard](https://use.appwarden.io).`
);
await sleep(600);
console.log(
`[@appwarden] Create a pull request to your main branch and follow the instructions from the Appwarden bot.`
);
await sleep(600);
console.log(`[@appwarden] Learn more at https://appwarden.io/docs/getting-started.`);
await sleep();
console.log(`[@appwarden] Thanks for using Appwarden, goodbye!`);
}
};
// src/commands/index.ts
var cli = {
name: "appwarden",
description: "CLI tool to manage Appwarden configuration files and deployments. Learn more at https://appwarden.io/docs."
};
// src/version.ts
import { createRequire } from "module";
var require2 = createRequire(import.meta.url);
var { version } = require2("../package.json");
var CLI_VERSION = version;
// src/runner/index.ts
var registerCommand = (program, cmd) => {
const commandInstance = program.command(cmd.command).description(cmd.description);
for (let i = 0; i < cmd.options.length; i += 2) {
commandInstance.option(cmd.options[i], cmd.options[i + 1]);
}
commandInstance.action(cmd.action);
};
var ansiColors = {
cyan: "\x1B[36m",
blue: "\x1B[34m",
reset: "\x1B[0m"
};
var isColorDisabled = () => process.env.NO_COLOR !== void 0;
var colorize = (color, text) => {
if (isColorDisabled()) {
return text;
}
return `${ansiColors[color]}${text}${ansiColors.reset}`;
};
var palette = {
title: "cyan",
commandText: "blue"
};
var AppwardenHelp = class extends Help {
styleTitle = (str) => colorize(palette.title, str);
styleCommandText = (str) => colorize(palette.commandText, str);
styleCommandDescription = (str) => str;
formatHelp = (cmd, helper) => {
const termWidth = helper.padWidth(cmd, helper);
const helpWidth = helper.helpWidth || 80;
const itemIndentWidth = 2;
const itemSeparatorWidth = 2;
const formatItem = (term, description) => {
if (!description) {
return this.styleCommandText(term);
}
const paddedPlainTerm = term.padEnd(termWidth + itemSeparatorWidth);
const fullText = `${paddedPlainTerm}${this.styleCommandDescription(description)}`;
const wrapped = helper.wrap(
fullText,
helpWidth - itemIndentWidth,
termWidth + itemSeparatorWidth
);
const lines = wrapped.split("\n");
const coloredTerm = this.styleCommandText(term);
lines[0] = coloredTerm + lines[0].slice(term.length);
return lines.join("\n");
};
const formatList = (textArray) => {
return textArray.join("\n").replace(/^/gm, " ".repeat(itemIndentWidth));
};
const output = [];
output.push(this.styleTitle(`Usage: ${helper.commandUsage(cmd)}`), "");
const commandDescription = helper.commandDescription(cmd);
if (commandDescription.length > 0) {
output.push(helper.wrap(this.styleCommandDescription(commandDescription), helpWidth, 0), "");
}
const argumentList = helper.visibleArguments(cmd).map((argument) => {
return formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument));
});
if (argumentList.length > 0) {
output.push(this.styleTitle("Arguments:"), formatList(argumentList), "");
}
const optionList = helper.visibleOptions(cmd).map((option) => {
return formatItem(helper.optionTerm(option), helper.optionDescription(option));
});
if (optionList.length > 0) {
output.push(this.styleTitle("Options:"), formatList(optionList), "");
}
if (helper.showGlobalOptions) {
const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
return formatItem(helper.optionTerm(option), helper.optionDescription(option));
});
if (globalOptionList.length > 0) {
output.push(this.styleTitle("Global Options:"), formatList(globalOptionList), "");
}
}
const commandList = helper.visibleCommands(cmd).map((subCmd) => {
return formatItem(helper.subcommandTerm(subCmd), helper.subcommandDescription(subCmd));
});
if (commandList.length > 0) {
output.push(this.styleTitle("Commands:"), formatList(commandList), "");
}
return output.join("\n");
};
};
async function runner() {
const program = new Command().name(cli.name).description(cli.description).version(CLI_VERSION).option("--debug", "Enable debug output").hook("preAction", (thisCommand) => {
setDebug(!!thisCommand.opts().debug);
});
program.configureHelp(new AppwardenHelp());
registerCommand(program, syncCommand);
registerCommand(program, domainsViewCommand);
program.parse(process.argv);
}
// src/index.ts
setGlobalDispatcher(
new Agent({
allowH2: true,
// Enable HTTP/2 over TLS (via ALPN negotiation)
connections: 1
// Use single multiplexed connection (required for HTTP/2 with Agent)
})
);
import("dotenv/config");
var run = async function() {
await runner();
};
run();